如何获得python类的所有方法与给定的装饰 [英] Howto get all methods of a python class with given decorator

查看:199
本文介绍了如何获得python类的所有方法与给定的装饰的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

 如何获取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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆