How to get all methods of a given class A that are decorated with the @decorator2?
class A():
def method_a(self):
pass
@decorator1
def method_b(self, b):
pass
@decorator2
def method_c(self, t=5):
pass
How to get all methods of a given class A that are decorated with the @decorator2?
class A():
def method_a(self):
pass
@decorator1
def method_b(self, b):
pass
@decorator2
def method_c(self, t=5):
pass
Method 1: Basic registering decorator
I already answered this question here: Calling functions by array index in Python =)
Method 2: Sourcecode parsing
If you do not have control over the class definition, which is one interpretation of what you'd like to suppose, this is impossible (without code-reading-reflection), since for example the decorator could be a no-op decorator (like in my linked example) that merely returns the function unmodified. (Nevertheless if you allow yourself to wrap/redefine the decorators, see Method 3: Converting decorators to be "self-aware", then you will find an elegant solution)
It is a terrible terrible hack, but you could use the
inspect
module to read the sourcecode itself, and parse it. This will not work in an interactive interpreter, because the inspect module will refuse to give sourcecode in interactive mode. However, below is a proof of concept.It works!:
Note that one has to pay attention to parsing and the python syntax, e.g.
@deco
and@deco(...
are valid results, but@deco2
should not be returned if we merely ask for'deco'
. We notice that according to the official python syntax at http://docs.python.org/reference/compound_stmts.html decorators are as follows:We breathe a sigh of relief at not having to deal with cases like
@(deco)
. But note that this still doesn't really help you if you have really really complicated decorators, such as@getDecorator(...)
, e.g.Thus, this best-that-you-can-do strategy of parsing code cannot detect cases like this. Though if you are using this method, what you're really after is what is written on top of the method in the definition, which in this case is
getDecorator
.According to the spec, it is also valid to have
@foo1.bar2.baz3(...)
as a decorator. You can extend this method to work with that. You might also be able to extend this method to return a<function object ...>
rather than the function's name, with lots of effort. This method however is hackish and terrible.Method 3: Converting decorators to be "self-aware"
If you do not have control over the decorator definition (which is another interpretation of what you'd like), then all these issues go away because you have control over how the decorator is applied. Thus, you can modify the decorator by wrapping it, to create your own decorator, and use that to decorate your functions. Let me say that yet again: you can make a decorator that decorates the decorator you have no control over, "enlightening" it, which in our case makes it do what it was doing before but also append a
.decorator
metadata property to the callable it returns, allowing you to keep track of "was this function decorated or not? let's check function.decorator!". And then you can iterate over the methods of the class, and just check to see if the decorator has the appropriate.decorator
property! =) As demonstrated here:Demonstration for
@decorator
:It works!:
However, a "registered decorator" must be the outermost decorator, otherwise the
.decorator
attribute annotation will be lost. For example in a train ofyou can only see metadata that
decoOutermost
exposes, unless we keep references to "more-inner" wrappers.sidenote: the above method can also build up a
.decorator
that keeps track of the entire stack of applied decorators and input functions and decorator-factory arguments. =) For example if you consider the commented-out lineR.original = func
, it is feasible to use a method like this to keep track of all wrapper layers. This is personally what I'd do if I wrote a decorator library, because it allows for deep introspection.There is also a difference between
@foo
and@bar(...)
. While they are both "decorator expressons" as defined in the spec, note thatfoo
is a decorator, whilebar(...)
returns a dynamically-created decorator, which is then applied. Thus you'd need a separate functionmakeRegisteringDecoratorFactory
, that is somewhat likemakeRegisteringDecorator
but even MORE META:Demonstration for
@decorator(...)
:This generator-factory wrapper also works:
bonus Let's even try the following with Method #3:
Result:
As you can see, unlike method2, @deco is correctly recognized even though it was never explicitly written in the class. Unlike method2, this will also work if the method is added at runtime (manually, via a metaclass, etc.) or inherited.
Be aware that you can also decorate a class, so if you "enlighten" a decorator that is used to both decorate methods and classes, and then write a class within the body of the class you want to analyze, then
methodsWithDecorator
will return decorated classes as well as decorated methods. One could consider this a feature, but you can easily write logic to ignore those by examining the argument to the decorator, i.e..original
, to achieve the desired semantics.Maybe, if the decorators are not too complex (but I don't know if there is a less hacky way).
To expand upon @ninjagecko's excellent answer in Method 2: Source code parsing, you can use the
ast
module introduced in Python 2.6 to perform self-inspection as long as the inspect module has access to the source code.I added a slightly more complicated decorated method:
Results: