Method 1: Basic registration registrar
I already answered this question here: Calling functions by array index in Python =)
Method 2: Parsing Source Code
If you don't have control over the class definition, which is one interpretation of what you would like to suggest, this is impossible (without reading the code, reflection), because, for example, a decorator may be a non-op decorator (as in my related example) which simply returns an unmodified function. (However, if you allow yourself to wrap / redefine decorators, see Method 3: Convert Decorators to "Self-Identity" , then you will find an elegant solution)
This is a terrible terrible hack, but you can use the inspect module to read the source code itself and analyze it. This will not work in the interactive interpreter, because the verification module will refuse to provide the source code interactively. However, below is a proof of concept.
#!/usr/bin/python3 import inspect def deco(func): return func def deco2(): def wrapper(func): pass return wrapper class Test(object): @deco def method(self): pass @deco2() def method2(self): pass def methodsWithDecorator(cls, decoratorName): sourcelines = inspect.getsourcelines(cls)[0] for i,line in enumerate(sourcelines): line = line.strip() if line.split('(')[0].strip() == '@'+decoratorName:
It works!:
>>> print(list( methodsWithDecorator(Test, 'deco') )) ['method']
Note that you need to pay attention to parsing and python syntax, for example. @deco and @deco(... are valid results, but @deco2 should not be returned if we just request 'deco' . We note that according to the official python syntax at http://docs.python.org/reference/compound_stmts .html decorators look like this:
decorator ::= "@" dotted_name ["(" [argument_list [","]] ")"] NEWLINE
We breathed a sigh of relief, not dealing with cases like @(deco) . But note that this still will not help you if you really have very complex decorators like @getDecorator(...) for example.
def getDecorator(): return deco
Therefore, this strategy that is best for you cannot detect such cases. Although, if you use this method, what you really need is what is written on top of the method in the definition, which in this case is getDecorator .
According to the specification, it is also fair to have @foo1.bar2.baz3(...) as a decorator. You can extend this method to work with this. You can also extend this method to return a <function object ...> , rather than a function name, with great difficulty. However, this method is hacky and terrible.
Method 3: Convert Decorators to Self-Identity
If you do not control the definition of decorator (which is a different interpretation of what you would like), then all these problems disappear because you control how the decorator is applied. Thus, you can change the decorator by wrapping it, create your own decorator and use it to decorate your functions. Let me say that again: you can make a decorator that decorates a decorator that you don’t control, enlighten it, which in our case does as it did before, but also adds the .decorator metadata .decorator to the one it .decorator , allowing you to track "was this function decorated or not? let check function.decorator!". And then , you can iterate over class methods and just check if the decorator has the corresponding .decorator property! =) As shown here:
def makeRegisteringDecorator(foreignDecorator): """ Returns a copy of foreignDecorator, which is identical in every way(*), except also appends a .decorator property to the callable it spits out. """ def newDecorator(func):
Demo for @decorator :
deco = makeRegisteringDecorator(deco) class Test2(object): @deco def method(self): pass @deco2() def method2(self): pass def methodsWithDecorator(cls, decorator): """ Returns all methods in CLS with DECORATOR as the outermost decorator. DECORATOR must be a "registering decorator"; one can make any decorator "registering" via the makeRegisteringDecorator function. """ for maybeDecorated in cls.__dict__.values(): if hasattr(maybeDecorated, 'decorator'): if maybeDecorated.decorator == decorator: print(maybeDecorated) yield maybeDecorated
It works!:
>>> print(list( methodsWithDecorator(Test2, deco) )) [<function method at 0x7d62f8>]
However, the “registered decorator” must be an external designer , otherwise the .decorator annotation will be lost. For example, in a train
@decoOutermost @deco @decoInnermost def func(): ...
you can only see the metadata that decoOutermost provides if we do not reference the “more internal” wrappers.
sidenote: the above method can also create a .decorator that tracks the entire stack of applied decorators and input functions and decorator-factory arguments . =) For example, if you are considering a missing line R.original = func , you can use this method to track all layers of the shell. This is personally what I would do if I wrote a decorator library, because it allows a deep introspection.
There is also a difference between @foo and @bar(...) . While they are "decorator idols," as defined in the specification, note that foo is a decorator, and bar(...) returns a dynamically created decorator, which is then applied. Thus, you will need a separate makeRegisteringDecoratorFactory function, which is somewhat similar to makeRegisteringDecorator , but even MORE META:
def makeRegisteringDecoratorFactory(foreignDecoratorFactory): def newDecoratorFactory(*args, **kw): oldGeneratedDecorator = foreignDecoratorFactory(*args, **kw) def newGeneratedDecorator(func): modifiedFunc = oldGeneratedDecorator(func) modifiedFunc.decorator = newDecoratorFactory
Demo for @decorator(...) :
def deco2(): def simpleDeco(func): return func return simpleDeco deco2 = makeRegisteringDecoratorFactory(deco2) print(deco2.__name__)
This shell generator factory also works:
>>> print(f.decorator) <function deco2 at 0x6a6408>
bonus Even try the following with method # 3:
def getDecorator():
Result:
>>> print(list( methodsWithDecorator(Test3, deco) )) [<function method at 0x7d62f8>]
As you can see, unlike method2, @deco is correctly recognized, although it was never explicitly written in the class. Unlike method2, this will also work if the method is added at runtime (manually, through a metaclass, etc.) or inherited.
Remember that you can also decorate a class, so if you enlighten a decorator that is used to decorate methods and classes, then write the class in the body of the class you want to analyze, then methodsWithDecorator will return the decorated classes, and also decorated methods. You can consider this function, but you can easily write logic to ignore it by examining the argument to the decorator, i.e. .original , to achieve the desired semantics.