如何获得python类的所有方法与给定的装饰 [英] Howto get all methods of a python class with given decorator
问题描述
如何获取A类的所有方法?
def method_a(self):
pass
@ decorator1
def method_b(self,b):
pass
@ decorator2
def method_c(self,t = 5):
pass
方法1:基本注册装饰器
我已经在这里回答这个问题:通过Python中的数组索引调用函数 =)
< hr>
方法2:源代码解析
如果您无法控制 strong> definition ,这是你想要假设的一个解释,这是不可能(没有代码阅读反射),因为例如装饰器可以是一个no- op装饰器(像在我的链接示例中),只返回未修改的函数。 (不过,如果您允许自己换行/重新定义装饰器,请参阅方法3:将装饰器转换为自我感知,然后您会找到一个优雅的解决方案)
这是一个可怕的可怕的黑客,但你可以使用 inspect
模块来读取源代码本身,并解析它。这不会在交互式解释器中工作,因为检查模块将拒绝以交互模式提供源代码。但是,下面是一个概念的证明。
#!/ usr / bin / python3
import检查
def deco(func):
return func
def deco2():
def wrapper(func):
pass
return wrapper
class Test(object):
@deco
def方法(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()
如果line.split('(')[0] .strip()=='@'+ decoratorName :#离开一点
nextLine = sourcelines [i + 1]
name = nextLine.split('def')[1] .split('(')[0] .strip b $ b yield(name)
它可以工作!:
>>> print(list(methodsWithDecorator(Test,'deco')))
['method']
请注意,必须注意解析和python语法,例如 @deco
和 @deco(...
是有效的结果,但 @ deco2
不应该返回,如果我们只要求
'deco'
我们注意到根据官方python语法 http://docs.python.org/reference/compound_stmts.html 修饰符如下:
decorator :: =@dotted_name [([argument_list [,]])] NEWLINE
我们呼吁不必处理 @(deco)
的情况,这仍然不真正帮助你,如果你真的很复杂的装饰,如 @getDecorator(...)
,例如
def getDecorator():
return deco
$ b b
因此,这种最好的,你可以做的解析代码的策略不能检测这样的情况。虽然如果你使用这个方法,你真正的后面是什么是写在方法的顶部该定义,在这种情况下是 getDecorator
。
根据规范,它也有效的 @ foo1.bar2.baz3(...)
作为装饰器。您可以扩展此方法以使用它。您还可以扩展此方法,以返回一个< function object ...>
,而不是函数的名称,大量的努力。
方法3:将装饰器转换为自我意识
如果您无法控制装饰定义(这是您想要的另一种解释),那么所有这些问题消失了,因为你可以控制装饰器的应用。因此,您可以通过换行来修改装饰器,以创建您自己的装饰器,并使用 装饰您的功能。让我再说一遍:你可以使装饰器装饰你不能控制的装饰器,启发它,在我们的情况下,它做它在做之前,但也追加 .decorator
元数据属性到它返回的可调用,允许你跟踪这个函数是否装饰?让我们检查function.decorator!然后然后,你可以迭代类的方法,只需检查装饰器是否有适当的 .decorator
属性! =)如下所示:
def makeRegisteringDecorator(foreignDecorator):
返回副本的foreignDecorator,它在每个
方法(*)是相同的,除了还附加一个.decorator属性到可调用它
spits out。
def newDecorator ):
#调用newDecorator(方法)
#完全像旧的装饰器,但输出跟踪装饰的内容
R = foreignDecorator(func)#apply foreignDecorator,像调用foreignDecorator方法)会做的
R.decorator = newDecorator#保持装饰器轨道
#R.original = func#可以跟踪一切!
return R
newDecorator .__ name__ = foreignDecorator .__ name__
newDecorator .__ doc__ = foreignDecorator .__ doc__
#(*)我们可以有点卫生,但是newDecorator仍然不是签名保留,即您将不能获得参数的运行时列表。为此,你需要hackish库...但在这种情况下,唯一的参数是func,所以这不是一个大问题
return newDecorator
@decorator
的演示:
deco = makeRegisteringDecorator(deco)
class Test2(object):
@deco
def method(self):
pass
@ deco2()
def method2(self):
pass
def methodsWithDecorator(cls,decorator):
返回CLS中的所有方法,使用DECORATOR作为
最外面的装饰器
DECORATOR必须是一个注册装饰器;一个
可以使任何装饰器通过
makeRegisteringDecorator函数
用于可能在cls .__ dict中指定的值__。values():
如果hasattr(maybeDecorated,'decorator'):
if maybeDecorated。 decorator == decorator:
print(maybeDecorated)
yield maybeDecorated
!:
>>> print(list(methodsWithDecorator(Test2,deco)))
[<函数方法在0x7d62f8>]
$ b b
但是,注册装饰器必须是最外面的装饰器,否则 .decorator
属性注释将丢失。例如:
@decoOutmost
@deco
@decoInnmost
def func ():...
您只能看到 decoOutmost <
sidenote:上面的方法也可以建立一个 .decorator
跟踪整个堆栈的应用装饰器和输入函数以及装饰器工厂参数。 =)例如,如果你考虑注释行 R.original = func
,可以使用这样的方法来跟踪所有包装器层。这是我个人如果我写一个装饰器库,因为它允许深入内省,如果我会做。
@foo
和 @bar(...)
。虽然它们都是规范中定义的装饰器表达式,但注意 foo
是装饰器,而 bar(...)
返回一个动态创建的装饰器,然后应用它。因此你需要一个单独的函数 makeRegisteringDecoratorFactory
,这有点像 makeRegisteringDecorator
但更多的META:
def makeRegisteringDecoratorFactory(foreignDecoratorFactory):
def newDecoratorFactory(* args,** kw):
oldGeneratedDecorator = foreignDecoratorFactory args,** kw)
def newGeneratedDecorator(func):
modifiedFunc = oldGeneratedDecorator(func)
modifiedFunc.decorator = newDecoratorFactory#跟踪装饰器
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__)
#结果:'deco2'
@ deco2()
def f():
pass
这个生成器工厂包装器也可以工作:
>>> print(f.decorator)
< function deco2 at 0x6a6408>
奖金我们甚至尝试以下方法#3:
def getDecorator():#让我们做一些调度!
return deco
class Test3(object):
@getDecorator()
def方法(self):
pass
$ b b @ deco2()
def method2(self):
pass
结果:
>>> print(list(methodsWithDecorator(Test3,deco)))
[< function method at 0x7d62f8>]
$ b b
正如你可以看到,与method2不同,@deco被正确识别,即使它从来没有明确写入类。与method2不同,如果方法是在运行时添加(手动,通过元类等)或继承的话,这也将工作。
请注意,类,所以如果你enlighten一个装饰器,用于装饰方法和类,然后在你想要分析的类的主体内写一个类 ,然后 methodsWithDecorator
将返回装饰的类以及装饰的方法。可以考虑这个特性,但是你可以通过检查装饰器的参数,即 .original
来轻松编写逻辑来忽略这些特性,以实现所需的语义。
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.
#!/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)
It works!:
>>> print(list( methodsWithDecorator(Test, 'deco') ))
['method']
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:
decorator ::= "@" dotted_name ["(" [argument_list [","]] ")"] NEWLINE
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.
def getDecorator():
return deco
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:
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, i.e. 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
Demonstration 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, a "registered decorator" must be the outermost decorator, otherwise the .decorator
attribute annotation will be lost. For example in a train of
@decoOutermost
@deco
@decoInnermost
def func(): ...
you 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 line R.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 that foo
is a decorator, while bar(...)
returns a dynamically-created decorator, which is then applied. Thus you'd need a separate function makeRegisteringDecoratorFactory
, that is somewhat like 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 # keep track of decorator
return modifiedFunc
return newGeneratedDecorator
newDecoratorFactory.__name__ = foreignDecoratorFactory.__name__
newDecoratorFactory.__doc__ = foreignDecoratorFactory.__doc__
return newDecoratorFactory
Demonstration for @decorator(...)
:
def deco2():
def simpleDeco(func):
return func
return simpleDeco
deco2 = makeRegisteringDecoratorFactory(deco2)
print(deco2.__name__)
# RESULT: 'deco2'
@deco2()
def f():
pass
This generator-factory wrapper also works:
>>> print(f.decorator)
<function deco2 at 0x6a6408>
bonus Let's even try the following with Method #3:
def getDecorator(): # let's do some dispatching!
return deco
class Test3(object):
@getDecorator()
def method(self):
pass
@deco2()
def method2(self):
pass
Result:
>>> print(list( methodsWithDecorator(Test3, deco) ))
[<function method at 0x7d62f8>]
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.
这篇关于如何获得python类的所有方法与给定的装饰的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!