如何使用给定的装饰器获取python类的所有方法 [英] How to get all methods of a python class with given decorator

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

问题描述

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

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

推荐答案

方法1:基本注册装饰器

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

如果您无法控制定义(这是对您想要的假设的一种解释),则不可能 >(不带代码阅读反射),因为例如装饰器可以是无操作装饰器(例如在我的链接示例中),它仅返回未修改的函数. (尽管如此,如果您允许自己包装/重新定义装饰器,请参阅方法3:将装饰器转换为具有自我意识" ,那么您将找到一个优雅的解决方案)

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)

这是一个可怕的骇客,但是您可以使用inspect模块读取源代码本身并进行解析.这在交互式解释器中将不起作用,因为检查模块将拒绝以交互方式提供源代码.但是,以下是概念证明.

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)

有效!

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

请注意,您必须注意解析和python语法,例如@deco@deco(...是有效的结果,但是如果仅要求'deco',则不应该返回@deco2.我们注意到根据 http://docs.python.org/reference/compound_stmts的官方python语法.html 装饰器如下:

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

我们不必处理@(deco)之类的案件,便松了一口气.但是请注意,如果您有非常复杂的装饰器(例如@getDecorator(...),例如

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

因此,这种分析代码的尽力而为"策略无法检测到这种情况.尽管如果您正在使用此方法,那么您真正想要的是定义中该方法顶部的内容,在这种情况下为getDecorator.

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.

根据规范,将@foo1.bar2.baz3(...)作为装饰器也是有效的.您可以扩展此方法以使用该方法.您也可以花费很多精力来扩展此方法以返回<function object ...>而不是函数的名称.但是,这种方法是骇人听闻的,而且很糟糕.

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.

如果您无法控制装饰器定义(这是您想要的另一种解释),那么所有这些问题都会消失,因为您可以控制关于装饰器的应用方式.因此,您可以通过 wrapping 修饰装饰器,以创建您自己的装饰器,并使用 that 装饰您的功能.让我再说一遍:您可以制作一个装饰器来装饰您无法控制的装饰器,启发"它,在我们的情况下,它可以像以前一样做,但是附加一个.decorator元数据属性返回到可调用的它,使您可以跟踪此功能是否经过装饰?让我们检查一下function.decorator!".然后然后,您可以遍历类的方法,然后检查装饰器是否具有适当的.decorator属性! =)如此处所示:

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

@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属性注释将丢失.例如,在

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(): ...

除非我们保留对更内部"包装的引用,否则您只能看到decoOutermost公开的元数据.

you can only see metadata that decoOutermost exposes, unless we keep references to "more-inner" wrappers.

边注:上面的方法还可以构建.decorator,以跟踪整堆应用的装饰器,输入函数和装饰器工厂参数. =)例如,如果考虑注释掉的行R.original = func,使用这样的方法来跟踪所有包装层是可行的.如果我编写装饰器库,这将是我个人的工作,因为它可以进行深入的自省.

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.

@foo@bar(...)之间也有区别.虽然它们都是规范中定义的装饰器表达",但请注意foo是装饰器,而bar(...)返回动态创建的装饰器,然后将其应用.因此,您需要一个单独的函数makeRegisteringDecoratorFactory,该函数有点像makeRegisteringDecorator,但甚至需要更多的元数据:

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

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

奖金我们甚至可以使用方法3尝试以下操作:

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

结果:

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

如您所见,与method2不同,@ deco可以正确识别,即使它从未在类中明确编写.与method2不同,如果该方法是在运行时(手动地通过元类等)添加或继承的,则该方法也将起作用.

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.

请注意,您还可以装饰一个类,因此,如果您启发"用于装饰方法和类的装饰器,然后在要分析的类的主体内编写一个类 ,则methodsWithDecorator将返回修饰的类以及修饰的方法.人们可以认为这是一项功能,但是您可以通过检查装饰器的参数(.original)来轻松实现所需的语义,从而编写出忽略这些逻辑的逻辑.

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天全站免登陆