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.
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']
@register
decorator, we need a more general solution. –
End 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 :)
inspect.getsource()
seems to return with the spaces in front of the def wrapper
, which then gives an unexpected indent error on the ast.parse
call. –
Amendatory 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? –
Amendatory decorator_list
not an instance of ast.Call
? Is it necessary to check this? –
Laird ast.Call
check in your own code and see if you still get the desired results –
Megrim 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']
@register
decorator, we need a more general solution. –
End 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:]
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. :(
As Faisal notes, you could have the decorators themselves attach metadata to the function, but to my knowledge it isn't automatically done.
ast
can be used to parse source code.
import ast
with open('./tmp1.py') as f:
code_text = f.read()
parsed = ast.parse(code_text)
Here's my attempt at parsing the parsed object for decorators. This implementation recursively searches for applied decorators. It'll capture usage of @
on functions, classes and methods as well as when they're nested.
import ast
def flatten(decorator_list):
for d in decorator_list:
try:
yield d.id
except AttributeError:
yield d.func.id
def parse_decorables(body, indent=0):
for node in body:
if isinstance(node, (ast.FunctionDef, ast.ClassDef)):
print(' '*indent, node.name, '-', *flatten(node.decorator_list))
parse_decorables(node.body, indent+1)
with open('./tmp1.py') as f:
code_text = f.read()
parsed = ast.parse(code_text)
parse_decorables(parsed.body)
There's a few things to bear in mind when considering this question. A decorator may just add an attribute to the function and return it. At that point, there's no association between the decorator and the original function. Therefore, there's no way to dynamically inspect a function and ensure we're seeing all the decorators that were applied to it.
So we go to this ast
solution and inspect the code. However, it has it's own limitations. An imported decorator may apply more decorators to the function, this will not catch that. If you 'apply a decorator' the way it was done before @
was introduced (f = deco(f)), this will not catch that. So, really we're just capturing uses of @
in the source file, which may be suitable for your application.
It is impossible to do in a general way, because
@foo
def bar ...
is exactly the same as
def bar ...
bar = foo (bar)
You may do it in certain special cases, like probably @staticmethod
by analyzing function objects, but not better than that.
If you don't need to know the decorator names from anywhere in your code, but for example from within the method itself, here is a different solution which uses a class decorator:
from inspect import currentframe
class MyDecorator(object):
def __init__(self, *decorator_names):
self.decorator_names = decorator_names
def __call__(self, func):
def decorator_wrapper(*args, **kwargs):
decorator_results = []
for decorator_name in self.decorator_names:
try:
decorator = MyDecorator.__dict__[decorator_name]
except KeyError:
# raise some error if you like
pass
else:
decorator_results.append(decorator(self, func, *args, **kwargs))
result = return_sth_from(decorator_results)
return result
return decorator_wrapper
def some_decorator(self, func, *args, **kwargs):
print("I am some decorator.")
return func(*args, **kwargs)
def another_decorator(self, func, *args, **kwargs):
print("I am another decorator.")
return func(*args, **kwargs)
@MyDecorator("some_decorator")
def main():
decorator_frame = currentframe().f_back
print(decorator_frame.f_locals['self'].decorator_names)
which prints:
('some_decorator',)
In case you are unfamiliar with class decorators, you can read more on that here.
That's not possible in my opinion. A decorator is not some kind of attribute or meta data of a method. A decorator is a convenient syntax for replacing a function with the result of a function call. See http://docs.python.org/whatsnew/2.4.html?highlight=decorators#pep-318-decorators-for-functions-and-methods for more details.
You can't but even worse is there exists libraries to help hide the fact that you have decorated a function to begin with. See Functools or the decorator library (@decorator
if I could find it) for more information.
© 2022 - 2025 — McMap. All rights reserved.
@require_http_methods(['GET', 'POST', ])
. In addition to printing out a docstring for a method, it could be nice to print out the http_methods that a method implements. – Callum