How To List All Exceptions A Function Could Raise In Python 3?
Solution 1:
You can't get reliable results for some (if not most) functions. Some examples:
functions that execute arbitrary code (e.g.
exec(')(rorrEeulaV esiar'[::-1])
raisesValueError
)functions that aren't written in Python
functions that call other functions that can propagate errors to the caller
functions re-raising active exceptions in the
except:
block
Unfortunately, this list is incomplete.
E.g. os.makedirs
is written in Python and you can see its source:
...
try:
mkdir(name, mode)
except OSError as e:
ifnot exist_ok or e.errno != errno.EEXIST ornot path.isdir(name):
raise
Bare raise
re-raises the last active exception (OSError
or one of its subclasses). Here's the class hierarchy for OSError
:
+-- OSError
| +-- BlockingIOError
| +-- ChildProcessError
| +-- ConnectionError
| | +-- BrokenPipeError
| | +-- ConnectionAbortedError
| | +-- ConnectionRefusedError
| | +-- ConnectionResetError
| +-- FileExistsError
| +-- FileNotFoundError
| +-- InterruptedError
| +-- IsADirectoryError
| +-- NotADirectoryError
| +-- PermissionError
| +-- ProcessLookupError
| +-- TimeoutError
To get the exact exception types you'll need to look into mkdir
, functions it calls, functions those functions call etc.
So, getting possible exceptions without running the function is very hard and you really should not do it.
However for simple cases like
raise Exception# without arguments
raise Exception('abc') # with arguments
a combination of ast
module functionality and inspect.getclosurevars
(to get exception classes, was introduced in Python 3.3) can produce quite accurate results:
from inspect import getclosurevars, getsource
from collections import ChainMap
from textwrap import dedent
import ast, os
classMyException(Exception):
passdefg():
raise Exception
classA():
defmethod():
raise OSError
deff(x):
int()
A.method()
os.makedirs()
g()
raise MyException
raise ValueError('argument')
defget_exceptions(func, ids=set()):
try:
vars = ChainMap(*getclosurevars(func)[:3])
source = dedent(getsource(func))
except TypeError:
returnclass_visitor(ast.NodeTransformer):
def__init__(self):
self.nodes = []
self.other = []
defvisit_Raise(self, n):
self.nodes.append(n.exc)
defvisit_Expr(self, n):
ifnotisinstance(n.value, ast.Call):
return
c, ob = n.value.func, Noneifisinstance(c, ast.Attribute):
parts = []
whilegetattr(c, 'value', None):
parts.append(c.attr)
c = c.value
if c.idinvars:
ob = vars[c.id]
for name inreversed(parts):
ob = getattr(ob, name)
elifisinstance(c, ast.Name):
if c.idinvars:
ob = vars[c.id]
if ob isnotNoneandid(ob) notin ids:
self.other.append(ob)
ids.add(id(ob))
v = _visitor()
v.visit(ast.parse(source))
for n in v.nodes:
ifisinstance(n, (ast.Call, ast.Name)):
name = n.idifisinstance(n, ast.Name) else n.func.idif name invars:
yieldvars[name]
for o in v.other:
yieldfrom get_exceptions(o)
for e in get_exceptions(f):
print(e)
prints
<class'__main__.MyException'>
<class'ValueError'>
<class'OSError'>
<class'Exception'>
Keep in mind that this code only works for functions written in Python.
Solution 2:
Finding Exception in non built-in source code:
As said in the topic Python: How can I know which exceptions might be thrown from a method call, you can get the Abstract Syntax Tree and search for raised exceptions.
import ast
deffind_raise(body):
raises = []
for ast_ in body:
ifisinstance(ast_, ast.Raise):
raises.append(ast_)
ifhasattr(ast_, 'body'):
raises += find_raise(ast_.body)
returnlist(set(raises))
test = '''
def f(arg):
raise OSError(arg)
'''
raises = find_raise(ast.parse(test).body)
print [i.type.func.idfor i in raises] # print ['OSError']
This method works for every piece of code that you have written.
Finding Exception in Built-in methods
You cannot parse built-in function like os.makedirs
.
Two alternatives:
- You can have a look at the tests included in your python distribution (ex with cpython)
- and if your target method offers python source code, you can parse it like previously (the code would be in /usr/lib/python3/*.py)
For all native C methods, you are stuck with the documentation and should trust it. When os.makedirs
says it only returns OSError
, it is true, since PermissionError
and FileExistError
exceptions are subclasses of OSError
.
To find Errors programmatically for built-in you can use this example:
>>> import re
>>> re.findall(r'\w+Error', open.__doc__)
['IOError', 'FileExistsError', 'ValueError']
>>> re.findall(r'\w+Error', os.makedirs.__doc__)
['OSError']
It catches all exceptions with a name ending with 'Error', it surely can be extended to find all standard exceptions.
Post a Comment for "How To List All Exceptions A Function Could Raise In Python 3?"