Introspection to get decorator names on a method?
Solution 1
If you can change the way you call the decorators from
class Foo(object):
@many
@decorators
@here
def bar(self):
pass
to
class Foo(object):
@register(many,decos,here)
def bar(self):
pass
then you could register the decorators this way:
def register(*decorators):
def register_wrapper(func):
for deco in decorators[::-1]:
func=deco(func)
func._decorators=decorators
return func
return register_wrapper
For example:
def many(f):
def wrapper(*args,**kwds):
return f(*args,**kwds)
return wrapper
decos = here = many
class Foo(object):
@register(many,decos,here)
def bar(self):
pass
foo=Foo()
Here we access the tuple of decorators:
print(foo.bar._decorators)
# (<function many at 0xb76d9d14>, <function decos at 0xb76d9d4c>, <function here at 0xb76d9d84>)
Here we print just the names of the decorators:
print([d.func_name for d in foo.bar._decorators])
# ['many', 'decos', 'here']
Solution 2
I'm surprised that this question is so old and no one has taken the time to add the actual introspective way to do this, so here it is:
The code you want to inspect...
def template(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
baz = template
che = template
class Foo(object):
@baz
@che
def bar(self):
pass
Now you can inspect the above Foo
class with something like this...
import ast
import inspect
def get_decorators(cls):
target = cls
decorators = {}
def visit_FunctionDef(node):
decorators[node.name] = []
for n in node.decorator_list:
name = ''
if isinstance(n, ast.Call):
name = n.func.attr if isinstance(n.func, ast.Attribute) else n.func.id
else:
name = n.attr if isinstance(n, ast.Attribute) else n.id
decorators[node.name].append(name)
node_iter = ast.NodeVisitor()
node_iter.visit_FunctionDef = visit_FunctionDef
node_iter.visit(ast.parse(inspect.getsource(target)))
return decorators
print get_decorators(Foo)
That should print something like this...
{'bar': ['baz', 'che']}
or at least it did when I tested this with Python 2.7.9 real quick :)
Solution 3
I've add the same question. In my unit tests I just wanted to make sure decorators were used by given functions/methods.
The decorators were tested separately so I didn't need to test the common logic for each decorated function, just that the decorators were used.
I finally came up with the following helper function:
import inspect
def get_decorators(function):
"""Returns list of decorators names
Args:
function (Callable): decorated method/function
Return:
List of decorators as strings
Example:
Given:
@my_decorator
@another_decorator
def decorated_function():
pass
>>> get_decorators(decorated_function)
['@my_decorator', '@another_decorator']
"""
source = inspect.getsource(function)
index = source.find("def ")
return [
line.strip().split()[0]
for line in source[:index].strip().splitlines()
if line.strip()[0] == "@"
]
With the list comprehension, it is a bit "dense" but it does the trick and in my case it's a test helper function.
It works if you are intrested only in the decorators names, not potential decorator arguments. If you want to support decorators taking arguments, something like line.strip().split()[0].split("(")[0]
could do the trick (untested)
Finally, you can remove the "@" if you'd like by replacing line.strip().split()[0]
by line.strip().split()[0][1:]
Solution 4
That's because decorators are "syntactic sugar". Say you have the following decorator:
def MyDecorator(func):
def transformed(*args):
print "Calling func " + func.__name__
func()
return transformed
And you apply it to a function:
@MyDecorator
def thisFunction():
print "Hello!"
This is equivalent to:
thisFunction = MyDecorator(thisFunction)
You could embed a "history" into the function object, perhaps, if you're in control of the decorators. I bet there's some other clever way to do this (perhaps by overriding assignment), but I'm not that well-versed in Python unfortunately. :(
Solution 5
As Faisal notes, you could have the decorators themselves attach metadata to the function, but to my knowledge it isn't automatically done.
Braedon Wooding
Updated on May 30, 2020Comments
-
Braedon Wooding about 4 years
I am trying to figure out how to get the names of all decorators on a method. I can already get the method name and docstring, but cannot figure out how to get a list of decorators.
-
Faisal almost 14 yearsThis is a great solution. :D It does assume you have access to the code that's assigning the decorators, though...
-
Braedon Wooding almost 14 yearsOk this could work, but why can't I just add the code func._whatever='something' into my existing decorator, and test for the value of the _whatever attribute when performing introspection on the method?
-
Faisal almost 14 yearsYou can, but then you'll have to dirty every decorator you write with the cross-cutting concern of leaving its tracks behind in the function it modifies.
-
Jmons over 6 yearsOkay, this has problems in python 3 : (at least it seems to, and I'm sure I'm not the best person with knowledge of inspect/ast to comment with that level of certainty); basically I have a the code from what you have above, and
inspect.getsource()
seems to return with the spaces in front of thedef wrapper
, which then gives an unexpected indent error on theast.parse
call. -
Jmons over 6 yearsALSO when i tried to demonstrate this using hte online run-script tools (which I think script rather then run from a file), I jsut get
OSError: source code not available
so I suspect there are instances (perhaps also bin runs) where this process won't work. Perhaps it won't work when bin-only runs of python exist? -
Jaymon over 6 yearsWhich version of python3? I just tested it in python 2.7.13 and python 3.6.4 (which are the versions I have on my computer) and they both worked fine. Also, I'm not sure this will work everywhere, but it's worked everywhere I've needed it. I could definitely see the online run-script having protections for modules like ast and inspect, and probably other things like opening files, since it is, by definition, a more contained environment.
-
Jmons over 6 yearsThanks for checking: I'll try to have another look but without being able to demonstrate with the nice helpful code sharing it makes it harder. If I can replicated it I'll open it up as a new question and tag you ;)
-
Stanislav Hordiyenko almost 4 yearsWorks like a charm!
-
ragazzojp over 2 yearsThis basically works only for your custom
@register
decorator, we need a more general solution.