如何获得给定的装饰器的Python类的所有方法

如何获得用@ decorator2装饰的给定类A的所有方法?

class A(): def method_a(self): pass @decorator1 def method_b(self, b): pass @decorator2 def method_c(self, t=5): pass 

方法1:基本注册装饰器

我已经在这里回答了这个问题: 通过Python中的数组索引调用函数 =)


方法2:源代码parsing

如果你不能控制类的定义 ,这是你想要的一个解释,这是不可能的 (没有代码阅读reflection),因为例如装饰器可能是一个无操作的装饰器(比如在我的链接的例子),只是返回未经修改的function。 (不过,如果你允许自己换装/重新定义装饰器,请参阅方法3:将装饰器转换为“自我感知” ,然后你会发现一个优雅的解决scheme)

这是一个可怕的黑客攻击,但你可以使用inspect模块来读取源代码本身,并parsing它。 这在交互式解释器中不起作用,因为检查模块将拒绝交互模式下的源代码。 但是,下面是一个概念的certificate。

 #!/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: # leaving a bit out nextLine = sourcelines[i+1] name = nextLine.split('def')[1].split('(')[0].strip() yield(name) 

有用!:

 >>> print(list( methodsWithDecorator(Test, 'deco') )) ['method'] 

注意,一个人必须注意parsing和Python语法,例如@deco@deco(...是有效的结果,但是@deco2不应该被返回,如果我们只要求'deco' 。我们注意到根据http://docs.python.org/reference/compound_stmts.html装饰器官方的Python语法如下:;

 decorator ::= "@" dotted_name ["(" [argument_list [","]] ")"] NEWLINE 

我们松了一口气,不必处理@(deco) 。 但是请注意,如果真的有复杂的装饰器,比如@getDecorator(...)

 def getDecorator(): return deco 

因此,这个parsing代码的“最佳可行”策略无法检测到这种情况。 虽然如果你正在使用这个方法,那么你真正要做的是在定义的方法之上写的东西,在这个例子中是getDecorator

根据规范,将@foo1.bar2.baz3(...)作为装饰器也是有效的。 你可以扩展这个方法来处理。 你也许可以扩展这个方法来返回一个<function object ...>而不是函数的名字。 然而这种方法是骇人而可怕的。


方法3:将装饰器转换为“自我感知”

如果你不能控制装饰器的定义 (这是你想要的另一种解释),那么所有这些问题都会消失,因为你可以控制装饰器的应用。 因此,你可以通过包装来修改装饰器,创build你自己的装饰器,并用来装饰你的函数。 让我再说一遍:你可以做一个装饰器来装饰你无法控制的装饰器,“启发”它,在我们的例子中,它使它做它以前做的事情,但是附加一个.decorator元数据属性给可调用的它返回,让你跟踪“是否这个函数装饰或不?我们来检查function.decorator!”。 然后你可以迭代类的方法,只需检查装饰器是否具有合适的.decorator属性! =)如这里所示:

 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): # Call to newDecorator(method) # Exactly like old decorator, but output keeps track of what decorated it R = foreignDecorator(func) # apply foreignDecorator, like call to foreignDecorator(method) would have done R.decorator = newDecorator # keep track of decorator #R.original = func # might as well keep track of everything! return R newDecorator.__name__ = foreignDecorator.__name__ newDecorator.__doc__ = foreignDecorator.__doc__ # (*)We can be somewhat "hygienic", but newDecorator still isn't signature-preserving, ie you will not be able to get a runtime list of parameters. For that, you need hackish libraries...but in this case, the only argument is func, so it's not a big issue return newDecorator 

演示@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 

有用!:

 >>> print(list( methodsWithDecorator(Test2, deco) )) [<function method at 0x7d62f8>] 

然而,“注册装饰器”必须是最外层的装饰器 ,否则.decorator属性注释将会丢失。 例如在一列火车上

 @decoOutermost @deco @decoInnermost def func(): ... 

除非我们继续引用“更内层”的包装器,否则你只能看到decoOutermost元数据。

旁注:上面的方法也可以build立一个.decorator ,跟踪整个应用装饰器和input函数和装饰器工厂参数的堆栈 =)例如,如果考虑注释行R.original = func ,可以使用像这样的方法来跟踪所有包装器层。 如果我编写了一个装饰器库,这就是我自己要做的事情,因为它允许深入的自省。

@foo@bar(...)也有区别。 虽然它们都是规范中定义的“装饰器expression式”,但请注意foo是装饰器,而bar(...)返回dynamic创build的装饰器,然后应用它。 因此,你需要一个单独的函数makeRegisteringDecoratorFactory ,这有点像makeRegisteringDecorator但甚至更多的makeRegisteringDecorator

 def makeRegisteringDecoratorFactory(foreignDecoratorFactory): def newDecoratorFactory(*args, **kw): oldGeneratedDecorator = foreignDecoratorFactory(*args, **kw) def newGeneratedDecorator(func): modifiedFunc = oldGeneratedDecorator(func) modifiedFunc.decorator = newDecoratorFactory # keep track of decorator return modifiedFunc return newGeneratedDecorator newDecoratorFactory.__name__ = foreignDecoratorFactory.__name__ newDecoratorFactory.__doc__ = foreignDecoratorFactory.__doc__ return newDecoratorFactory 

演示@decorator(...)

 def deco2(): def simpleDeco(func): return func return simpleDeco deco2 = makeRegisteringDecoratorFactory(deco2) print(deco2.__name__) # RESULT: 'deco2' @deco2() def f(): pass 

这个生成器工厂包装也可以工作:

 >>> print(f.decorator) <function deco2 at 0x6a6408> 

奖金让我们甚至尝试以下方法#3:

 def getDecorator(): # let's do some dispatching! return deco class Test3(object): @getDecorator() def method(self): pass @deco2() def method2(self): pass 

结果:

 >>> print(list( methodsWithDecorator(Test3, deco) )) [<function method at 0x7d62f8>] 

正如你所看到的,不像method2,@deco被正确识别,即使它从来没有明确写在类中。 与method2不同的是,如果方法是在运行时(手动,通过元类等等)添加或inheritance的,这也会起作用。

请注意,你也可以装饰一个类,所以如果你“启发”了一个用于装饰方法和类的装饰器,然后在要分析的类的主体中编写一个类,那么methodsWithDecorator将返回装饰类以及装饰方法。 人们可以认为这是一个特征,但是你可以很容易地写逻辑来忽略这些,通过检查装饰者的参数,即.original来实现所需的语义。

要扩展@ ninjagecko在方法2:源代码parsing中的出色答案,只要检查模块可以访问源代码,就可以使用Python 2.6中引入的ast模块来执行自检。

 def findDecorators(target): import ast, inspect res = {} def visit_FunctionDef(node): res[node.name] = [ast.dump(e) for e in node.decorator_list] V = ast.NodeVisitor() V.visit_FunctionDef = visit_FunctionDef V.visit(compile(inspect.getsource(target), '?', 'exec', ast.PyCF_ONLY_AST)) return res 

我添加了一个更复杂的装饰方法:

 @xydecorator2 def method_d(self, t=5): pass 

结果:

 > findDecorators(A) {'method_a': [], 'method_b': ["Name(id='decorator1', ctx=Load())"], 'method_c': ["Name(id='decorator2', ctx=Load())"], 'method_d': ["Attribute(value=Attribute(value=Name(id='x', ctx=Load()), attr='y', ctx=Load()), attr='decorator2', ctx=Load())"]} 

也许,如果装饰者不是太复杂(但我不知道是否有一个不太古怪的方式)。

 def decorator1(f): def new_f(): print "Entering decorator1", f.__name__ f() new_f.__name__ = f.__name__ return new_f def decorator2(f): def new_f(): print "Entering decorator2", f.__name__ f() new_f.__name__ = f.__name__ return new_f class A(): def method_a(self): pass @decorator1 def method_b(self, b): pass @decorator2 def method_c(self, t=5): pass print A.method_a.im_func.func_code.co_firstlineno print A.method_b.im_func.func_code.co_firstlineno print A.method_c.im_func.func_code.co_firstlineno