
Co-authored-by: Marco Borgeaud <89914223+marco-antognini-sonarsource@users.noreply.github.com>
165 lines
5.8 KiB
Plaintext
165 lines
5.8 KiB
Plaintext
== Why is this an issue?
|
||
|
||
Using ``++return++``, ``++break++`` or ``++continue++`` in a ``++finally++`` block suppresses the propagation of any unhandled exception which was raised in the ``++try++``, ``++else++`` or ``++except++`` blocks. It will also ignore their return statements.
|
||
|
||
|
||
``https://docs.python.org/3/library/exceptions.html#SystemExit[SystemExit]`` is raised when ``++sys.exit()++`` is called. ``https://docs.python.org/3/library/exceptions.html#KeyboardInterrupt[KeyboardInterrupt]`` is raised when the user asks the program to stop by pressing interrupt keys. Both exceptions are expected to propagate up until the application stops. It is ok to catch them when a clean-up is necessary but they should be raised again immediately. They should never be ignored.
|
||
|
||
|
||
If you need to ignore every other exception you can simply catch the ``++Exception++`` class. However you should be very careful when you do this as it will ignore other important exceptions such as ``https://docs.python.org/3/library/exceptions.html#MemoryError[MemoryError]``
|
||
|
||
|
||
In python 2 it is possible to raise old style classes. You can use a bare ``++except:++`` statement to catch every exception. Remember to still reraise ``++SystemExit++`` and ``++KeyboardInterrupt++``.
|
||
|
||
|
||
This rule raises an issue when a jump statement (``++break++``, ``++continue++``, ``++return++``) would force the control flow to leave a finally block.
|
||
|
||
=== Noncompliant code example
|
||
|
||
[source,python]
|
||
----
|
||
def find_file_which_contains(expected_content, paths):
|
||
file = None
|
||
for path in paths:
|
||
try:
|
||
# "open" will raise IsADirectoryError if the provided path is a directory but it will be stopped by the "return" and "continue"
|
||
file = open(path, 'r')
|
||
actual_content = file.read()
|
||
except FileNotFoundError as exception:
|
||
# This exception will never pass the "finally" block because of "return" and "continue"
|
||
raise ValueError(f"'paths' should only contain existing files. File ${path} does not exist.")
|
||
finally:
|
||
file.close()
|
||
if actual_content != expected_content:
|
||
# Note that "continue" is allowed in a "finally" block only since python 3.8
|
||
continue # Noncompliant. This will prevent exceptions raised by the "try" block and "except" block from raising.
|
||
else:
|
||
return path # Noncompliant. Same as for "continue"
|
||
return None
|
||
|
||
# This will return None instead of raising ValueError from the "except" block
|
||
find_file_which_contains("some content", ["file_which_does_not_exist"])
|
||
|
||
# This will return None instead of raising IsADirectoryError from the "try" block
|
||
find_file_which_contains("some content", ["a_directory"])
|
||
|
||
import sys
|
||
|
||
while True:
|
||
try:
|
||
sys.exit(1)
|
||
except (SystemExit) as e:
|
||
print("Exiting")
|
||
raise
|
||
finally:
|
||
break # This will prevent SystemExit from raising
|
||
|
||
def continue_whatever_happens_noncompliant():
|
||
for i in range(10):
|
||
try:
|
||
raise ValueError()
|
||
finally:
|
||
continue # Noncompliant
|
||
----
|
||
|
||
=== Compliant solution
|
||
|
||
[source,python]
|
||
----
|
||
# Note that using "with open(...) as" would be better. We keep the example as is just for demonstration purpose.
|
||
|
||
def find_file_which_contains(expected_content, paths):
|
||
file = None
|
||
for path in paths:
|
||
try:
|
||
file = open(path, 'r')
|
||
actual_content = file.read()
|
||
if actual_content != expected_content:
|
||
continue
|
||
else:
|
||
return path
|
||
except FileNotFoundError as exception:
|
||
raise ValueError(f"'paths' should only contain existing files. File ${path} does not exist.")
|
||
finally:
|
||
if file:
|
||
file.close()
|
||
return None
|
||
|
||
# This raises ValueError
|
||
find_file_which_contains("some content", ["file_which_does_not_exist"])
|
||
|
||
# This raises IsADirectoryError
|
||
find_file_which_contains("some content", ["a_directory"])
|
||
|
||
import sys
|
||
|
||
while True:
|
||
try:
|
||
sys.exit(1)
|
||
except (SystemExit) as e:
|
||
print("Exiting")
|
||
raise # SystemExit is re-raised
|
||
|
||
import logging
|
||
|
||
def continue_whatever_happens_compliant():
|
||
for i in range(10):
|
||
try:
|
||
raise ValueError()
|
||
except Exception:
|
||
logging.exception("Failed") # Ignore all "Exception" subclasses yet allow SystemExit and other important exceptions to pass
|
||
----
|
||
|
||
== Resources
|
||
|
||
* Python documentation - https://docs.python.org/3/reference/compound_stmts.html#except[the ``++try++`` statement]
|
||
|
||
ifdef::env-github,rspecator-view[]
|
||
|
||
'''
|
||
== Implementation Specification
|
||
(visible only on this page)
|
||
|
||
include::../message.adoc[]
|
||
|
||
'''
|
||
== Comments And Links
|
||
(visible only on this page)
|
||
|
||
=== on 3 Mar 2020, 17:27:45 Nicolas Harraudeau wrote:
|
||
Note: There is no need to create issues for ``++raise++`` statements because:
|
||
|
||
* there might be a good reason to raise an exception (Ex: cannot release resource)
|
||
* python will link the new exception and the old exception automatically:
|
||
|
||
----
|
||
In [1]: try:
|
||
...: raise ValueError("try block")
|
||
...: finally:
|
||
...: raise TypeError("finally block")
|
||
...:
|
||
---------------------------------------------------------------------------
|
||
ValueError Traceback (most recent call last)
|
||
<ipython-input-1-dcce5abc58e4> in <module>
|
||
1 try:
|
||
----> 2 raise ValueError("try block")
|
||
3 finally:
|
||
|
||
ValueError: try block
|
||
|
||
During handling of the above exception, another exception occurred:
|
||
|
||
TypeError Traceback (most recent call last)
|
||
<ipython-input-1-dcce5abc58e4> in <module>
|
||
2 raise ValueError("try block")
|
||
3 finally:
|
||
----> 4 raise TypeError("finally block")
|
||
5
|
||
|
||
TypeError: finally block
|
||
----
|
||
|
||
include::../comments-and-links.adoc[]
|
||
|
||
endif::env-github,rspecator-view[]
|