如何使用给定的装饰器获取Python类的所有方法?

发布于 2024-11-05 22:59:24 字数 260 浏览 6 评论 0原文

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

class A():
    def method_a(self):
      pass

    @decorator1
    def method_b(self, b):
      pass

    @decorator2
    def method_c(self, t=5):
      pass

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

单身情人 2024-11-12 22:59:24

方法1:基本注册装饰器

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


方法 2:源代码解析

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

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

#!/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.html 装饰器如下:

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

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

def getDecorator():
    return deco

因此,这种解析代码的最佳策略无法检测到这样的情况,但如果您使用此方法,您真正需要的是定义中方法之上编写的内容。在这种情况下是 getDecorator

根据规范,将 @foo1.bar2.baz3(...) 作为装饰器也是有效的。您可以扩展此方法来使用它。您还可以扩展此方法以返回 而不是函数名称,这需要花费大量精力。然而,这种方法是黑客且可怕的。


方法 3:将装饰器转换为“自我意识”

如果您无法控制装饰器定义(这是您想要的另一种解释),那么所有这些问题都会消失,因为您可以控制装饰器的应用方式。因此,您可以通过包装来修改装饰器,以创建您自己的装饰器,并使用that来装饰您的函数。让我再说一遍:您可以创建一个装饰器来装饰您无法控制的装饰器,“启发”它,在我们的例子中,这使它执行之前所做的事情,但附加一个它返回的可调用对象的 .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, 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< /code> 属性注释将会丢失。例如,在一列中,

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

您只能看到 decoOutermost 公开的元数据,除非我们保留对“更内部”包装器的引用。

旁注:上述方法还可以构建一个 .decorator 来跟踪应用的装饰器和输入函数以及装饰器工厂参数的整个堆栈。 =) 例如,如果您考虑注释掉的行 R.original = func,那么使用这样的方法来跟踪所有包装层是可行的。如果我编写一个装饰器库,这就是我个人会做的,因为它允许深入自省。

@foo@bar(.. .)。虽然它们都是规范中定义的“装饰器表达式”,但请注意 foo 是一个装饰器,而 bar(...) 返回一个动态创建的装饰器,它然后应用。因此,您需要一个单独的函数makeRegisteringDecoratorFactory,它有点像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>]

如您所见,与方法 2 不同,@deco 被正确识别,即使它从未明确写入班级。与 method2 不同,如果该方法是在运行时添加(手动、通过元类等)或继承的,这也将起作用。

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

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.

不喜欢何必死缠烂打 2024-11-12 22:59:24

为了扩展@ninjagecko在方法2:源代码解析中的优秀答案,只要inspect模块可以访问源代码,您就可以使用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

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

@x.y.decorator2
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())"]}

To expand upon @ninjagecko's excellent answer in Method 2: Source code parsing, you can use the ast module introduced in Python 2.6 to perform self-inspection as long as the inspect module has access to the source code.

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

I added a slightly more complicated decorated method:

@x.y.decorator2
def method_d(self, t=5): pass

Results:

> 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())"]}
樱桃奶球 2024-11-12 22:59:24

如果您确实可以控制装饰器,则可以使用装饰器类而不是函数:

class awesome(object):
    def __init__(self, method):
        self._method = method
    def __call__(self, obj, *args, **kwargs):
        return self._method(obj, *args, **kwargs)
    @classmethod
    def methods(cls, subject):
        def g():
            for name in dir(subject):
                method = getattr(subject, name)
                if isinstance(method, awesome):
                    yield name, method
        return {name: method for name,method in g()}

class Robot(object):
   @awesome
   def think(self):
      return 0

   @awesome
   def walk(self):
      return 0

   def irritate(self, other):
      return 0

如果我调用 awesome.methods(Robot) 它会返回

{'think': <mymodule.awesome object at 0x000000000782EAC8>, 'walk': <mymodulel.awesome object at 0x000000000782EB00>}

If you do have control over the decorators, you can use decorator classes rather than functions:

class awesome(object):
    def __init__(self, method):
        self._method = method
    def __call__(self, obj, *args, **kwargs):
        return self._method(obj, *args, **kwargs)
    @classmethod
    def methods(cls, subject):
        def g():
            for name in dir(subject):
                method = getattr(subject, name)
                if isinstance(method, awesome):
                    yield name, method
        return {name: method for name,method in g()}

class Robot(object):
   @awesome
   def think(self):
      return 0

   @awesome
   def walk(self):
      return 0

   def irritate(self, other):
      return 0

and if I call awesome.methods(Robot) it returns

{'think': <mymodule.awesome object at 0x000000000782EAC8>, 'walk': <mymodulel.awesome object at 0x000000000782EB00>}
可爱暴击 2024-11-12 22:59:24

对于我们这些只想要绝对最简单的情况的人 - 即单文件解决方案,我们可以完全控制我们正在使用的类和我们正在尝试跟踪的装饰器,我有一个答案。 ninjagecko 链接到了一个解决方案,用于当您可以控制要跟踪的装饰器时,但我个人发现它很复杂并且很难理解,可能是因为到目前为止我从未使用过装饰器。因此,我创建了以下示例,目标是尽可能简单明了。它是一个装饰器,一个具有多个装饰方法的类,以及用于检索+运行所有应用了特定装饰器的方法的代码。

# our decorator
def cool(func, *args, **kwargs):
    def decorated_func(*args, **kwargs):
        print("cool pre-function decorator tasks here.")
        return_value = func(*args, **kwargs)
        print("cool post-function decorator tasks here.")
        return return_value
    # add is_cool property to function so that we can check for its existence later
    decorated_func.is_cool = True
    return decorated_func

# our class, in which we will use the decorator
class MyClass:
    def __init__(self, name):
        self.name = name

    # this method isn't decorated with the cool decorator, so it won't show up 
    # when we retrieve all the cool methods
    def do_something_boring(self, task):
        print(f"{self.name} does {task}")
    
    @cool
    # thanks to *args and **kwargs, the decorator properly passes method parameters
    def say_catchphrase(self, *args, catchphrase="I'm so cool you could cook an egg on me.", **kwargs):
        print(f"{self.name} says \"{catchphrase}\"")

    @cool
    # the decorator also properly handles methods with return values
    def explode(self, *args, **kwargs):
        print(f"{self.name} explodes.")
        return 4

    def get_all_cool_methods(self):
        """Get all methods decorated with the "cool" decorator.
        """
        cool_methods =  {name: getattr(self, name)
                            # get all attributes, including methods, properties, and builtins
                            for name in dir(self)
                                # but we only want methods
                                if callable(getattr(self, name))
                                # and we don't need builtins
                                and not name.startswith("__")
                                # and we only want the cool methods
                                and hasattr(getattr(self, name), "is_cool")
        }
        return cool_methods

if __name__ == "__main__":
    jeff = MyClass(name="Jeff")
    cool_methods = jeff.get_all_cool_methods()    
    for method_name, cool_method in cool_methods.items():
        print(f"{method_name}: {cool_method} ...")
        # you can call the decorated methods you retrieved, just like normal,
        # but you don't need to reference the actual instance to do so
        return_value = cool_method()
        print(f"return value = {return_value}\n")

运行上面的示例会给出以下输出:

explode: <bound method cool.<locals>.decorated_func of <__main__.MyClass object at 0x00000220B3ACD430>> ...
cool pre-function decorator tasks here.
Jeff explodes.
cool post-function decorator tasks here.
return value = 4

say_catchphrase: <bound method cool.<locals>.decorated_func of <__main__.MyClass object at 0x00000220B3ACD430>> ...
cool pre-function decorator tasks here.
Jeff says "I'm so cool you could cook an egg on me."
cool post-function decorator tasks here.
return value = None

请注意,此示例中的修饰方法具有不同类型的返回值和不同的签名,因此能够检索和运行它们的实际价值有点可疑。但是,在有许多类似方法的情况下,所有方法都具有相同的签名和/或返回值类型(例如,如果您正在编写一个连接器来从一个数据库检索非标准化数据,对其进行标准化,然后将其插入到第二个数据库中)标准化数据库,并且您有一堆类似的方法,例如 15 个 read_and_normalize_table_X 方法),能够即时检索(并运行)它们可能会更有用。

For those of us who just want the absolute simplest possible case - namely, a single-file solution where we have total control over both the class we're working with and the decorator we're trying to track, I've got an answer. ninjagecko linked to a solution for when you have control over the decorator you want to track, but I personally found it to be complicated and really hard to understand, possibly because I've never worked with decorators until now. So, I've created the following example, with the goal of being as straightforward and simple as possible. It's a decorator, a class with several decorated methods, and code to retrieve+run all methods that have a specific decorator applied to them.

# our decorator
def cool(func, *args, **kwargs):
    def decorated_func(*args, **kwargs):
        print("cool pre-function decorator tasks here.")
        return_value = func(*args, **kwargs)
        print("cool post-function decorator tasks here.")
        return return_value
    # add is_cool property to function so that we can check for its existence later
    decorated_func.is_cool = True
    return decorated_func

# our class, in which we will use the decorator
class MyClass:
    def __init__(self, name):
        self.name = name

    # this method isn't decorated with the cool decorator, so it won't show up 
    # when we retrieve all the cool methods
    def do_something_boring(self, task):
        print(f"{self.name} does {task}")
    
    @cool
    # thanks to *args and **kwargs, the decorator properly passes method parameters
    def say_catchphrase(self, *args, catchphrase="I'm so cool you could cook an egg on me.", **kwargs):
        print(f"{self.name} says \"{catchphrase}\"")

    @cool
    # the decorator also properly handles methods with return values
    def explode(self, *args, **kwargs):
        print(f"{self.name} explodes.")
        return 4

    def get_all_cool_methods(self):
        """Get all methods decorated with the "cool" decorator.
        """
        cool_methods =  {name: getattr(self, name)
                            # get all attributes, including methods, properties, and builtins
                            for name in dir(self)
                                # but we only want methods
                                if callable(getattr(self, name))
                                # and we don't need builtins
                                and not name.startswith("__")
                                # and we only want the cool methods
                                and hasattr(getattr(self, name), "is_cool")
        }
        return cool_methods

if __name__ == "__main__":
    jeff = MyClass(name="Jeff")
    cool_methods = jeff.get_all_cool_methods()    
    for method_name, cool_method in cool_methods.items():
        print(f"{method_name}: {cool_method} ...")
        # you can call the decorated methods you retrieved, just like normal,
        # but you don't need to reference the actual instance to do so
        return_value = cool_method()
        print(f"return value = {return_value}\n")

Running the above example gives us the following output:

explode: <bound method cool.<locals>.decorated_func of <__main__.MyClass object at 0x00000220B3ACD430>> ...
cool pre-function decorator tasks here.
Jeff explodes.
cool post-function decorator tasks here.
return value = 4

say_catchphrase: <bound method cool.<locals>.decorated_func of <__main__.MyClass object at 0x00000220B3ACD430>> ...
cool pre-function decorator tasks here.
Jeff says "I'm so cool you could cook an egg on me."
cool post-function decorator tasks here.
return value = None

Note that the decorated methods in this example have different types of return values and different signatures, so the practical value of being able to retrieve and run them all is a bit dubious. However, in cases where there are many similar methods, all with the same signature and/or type of return value (like if you're writing a connector to retrieve unnormalized data from one database, normalize it, and insert it into a second, normalized database, and you have a bunch similar methods, e.g. 15 read_and_normalize_table_X methods), being able to retrieve (and run) them all on the fly could be more useful.

喜爱皱眉﹌ 2024-11-12 22:59:24

也许,如果装饰器不太复杂(但我不知道是否有一种不那么麻烦的方法)。

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

Maybe, if the decorators are not too complex (but I don't know if there is a less hacky way).

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
浮华 2024-11-12 22:59:24

我不想添加太多内容,只是 ninjagecko 方法 2 的一个简单变体。它的效果非常好。

相同的代码,但使用列表理解而不是生成器,这正是我所需要的。

def methodsWithDecorator(cls, decoratorName):

    sourcelines = inspect.getsourcelines(cls)[0]
    return [ sourcelines[i+1].split('def')[1].split('(')[0].strip()
                    for i, line in enumerate(sourcelines)
                    if line.split('(')[0].strip() == '@'+decoratorName]

I don't want to add much, just a simple variation of ninjagecko's Method 2. It works wonders.

Same code, but using list comprehension instead of a generator, which is what I needed.

def methodsWithDecorator(cls, decoratorName):

    sourcelines = inspect.getsourcelines(cls)[0]
    return [ sourcelines[i+1].split('def')[1].split('(')[0].strip()
                    for i, line in enumerate(sourcelines)
                    if line.split('(')[0].strip() == '@'+decoratorName]
寄居者 2024-11-12 22:59:24

解决此问题的一个简单方法是将代码放入装饰器中,将传入的每个函数/方法添加到数据集(例如列表)中。

例如

def deco(foo):
    functions.append(foo)
    return foo

,现在每个带有 deco 装饰器的函数都将被添加到 functions 中。

A simple way to solve this problem is to put code in the decorator that adds each function/method, that is passed in, to a data set (for example a list).

e.g.

def deco(foo):
    functions.append(foo)
    return foo

now every function with the deco decorator will be added to functions.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文