【问题标题】:Introspection to get decorator names on a method?内省以获取方法上的装饰器名称?
【发布时间】:2011-03-15 00:08:50
【问题描述】:

我试图弄清楚如何获取方法上所有装饰器的名称。我已经可以获取方法名称和文档字符串,但不知道如何获取装饰器列表。

【问题讨论】:

  • 这似乎没有必要。你有来源。看源码有什么问题?
  • @S.Lott:对于任何涉及自省的问题,您不能以同样的方式回答吗?然而,自省是有用的。
  • @S.Lott:阅读源代码没有问题,就像直接读取数据库内容而不是使用视图或脚本也没有问题一样,除非我想要自动化。我使用装饰器进行身份验证,并生成具有不同视图的报告,以显示哪些用户组可以访问哪些资源。所以我需要以编程方式访问源,就像我需要以编程方式访问数据源一样。
  • @Ned Batchelder:我不是在“回答”——至少我不这么认为。我在问用例是什么。内省是律师所说的“令人讨厌的麻烦”。我没有得到这个自省示例的用例。这个问题太短了,细节太薄了。
  • 这不是一个很有帮助的说明。你能提供一些用例和一些代码吗?

标签: python decorator introspection


【解决方案1】:

我很惊讶这个问题是如此古老,并且没有人花时间添加实际的内省方式来做到这一点,所以这里是:

您要检查的代码...

def template(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

baz = template
che = template

class Foo(object):
    @baz
    @che
    def bar(self):
        pass

现在您可以使用类似这样的方式检查上述Foo 类...

import ast
import inspect

def get_decorators(cls):
    target = cls
    decorators = {}

    def visit_FunctionDef(node):
        decorators[node.name] = []
        for n in node.decorator_list:
            name = ''
            if isinstance(n, ast.Call):
                name = n.func.attr if isinstance(n.func, ast.Attribute) else n.func.id
            else:
                name = n.attr if isinstance(n, ast.Attribute) else n.id

            decorators[node.name].append(name)

    node_iter = ast.NodeVisitor()
    node_iter.visit_FunctionDef = visit_FunctionDef
    node_iter.visit(ast.parse(inspect.getsource(target)))
    return decorators

print get_decorators(Foo)

应该打印出这样的东西...

{'bar': ['baz', 'che']}

或者至少当我用 Python 2.7.9 快速测试时它确实如此:)

【讨论】:

  • 好的,这在 python 3 中存在问题:(至少看起来是这样,而且我确定我 不是 了解 inspect/ast 的最佳人选以那种程度的确定性发表评论);基本上我有你上面的代码,inspect.getsource() 似乎返回 def wrapper 前面的空格,然后在 ast.parse 调用上出现意外的缩进错误。
  • ALSO 当我尝试使用 hte 在线运行脚本工具(我认为是脚本而不是从文件运行)来演示这一点时,我得到了 OSError: source code not available 所以我怀疑在某些情况下(可能还有 bin 运行)此过程不起作用。当 python 的 bin-only 运行存在时,它可能不起作用?
  • python3的哪个版本?我刚刚在 python 2.7.13 和 python 3.6.4 (这是我电脑上的版本)中对其进行了测试,它们都运行良好。此外,我不确定这是否适用于任何地方,但它适用于我需要它的任何地方。我绝对可以看到在线运行脚本对 ast 和 inspect 之类的模块具有保护,可能还有其他诸如打开文件之类的东西,因为根据定义,它是一个包含更多内容的环境。
  • 感谢您的检查:我将尝试再看一遍,但无法通过有用的代码共享来演示,这使得它变得更加困难。如果我可以复制它,我会将它作为一个新问题打开并标记你;)
【解决方案2】:

如果你可以改变调用装饰器的方式

class Foo(object):
    @many
    @decorators
    @here
    def bar(self):
        pass

class Foo(object):
    @register(many,decos,here)
    def bar(self):
        pass

那么你可以这样注册装饰器:

def register(*decorators):
    def register_wrapper(func):
        for deco in decorators[::-1]:
            func=deco(func)
        func._decorators=decorators        
        return func
    return register_wrapper

例如:

def many(f):
    def wrapper(*args,**kwds):
        return f(*args,**kwds)
    return wrapper

decos = here = many

class Foo(object):
    @register(many,decos,here)
    def bar(self):
        pass

foo=Foo()

这里我们访问装饰器的元组:

print(foo.bar._decorators)
# (<function many at 0xb76d9d14>, <function decos at 0xb76d9d4c>, <function here at 0xb76d9d84>)

这里我们只打印装饰器的名称:

print([d.func_name for d in foo.bar._decorators])
# ['many', 'decos', 'here']

【讨论】:

  • 这是一个很好的解决方案。 :D 它确实假设您可以访问分配装饰器的代码,但是...
  • 好的,这可以工作,但是为什么我不能将代码 func._whatever='something' 添加到我现有的装饰器中,并在对方法执行自省时测试 _whatever 属性的值?
  • 你可以,但是你必须弄脏你写的每一个装饰器,担心在它修改的函数中留下它的轨迹。
  • 这基本上只适用于您自定义的@register 装饰器,我们需要一个更通用的解决方案。
【解决方案3】:

我添加了同样的问题。在我的单元测试中,我只是想确保给定的函数/方法使用装饰器。

装饰器是单独测试的,所以我不需要测试每个装饰函数的通用逻辑,只需要使用装饰器即可。

我终于想出了以下辅助函数:

import inspect

def get_decorators(function):
    """Returns list of decorators names

    Args:
        function (Callable): decorated method/function

    Return:
        List of decorators as strings

    Example:
        Given:

        @my_decorator
        @another_decorator
        def decorated_function():
            pass

        >>> get_decorators(decorated_function)
        ['@my_decorator', '@another_decorator']

    """
    source = inspect.getsource(function)
    index = source.find("def ")
    return [
        line.strip().split()[0]
        for line in source[:index].strip().splitlines()
        if line.strip()[0] == "@"
    ]

使用列表理解,它有点“密集”,但它可以解决问题,在我的情况下,它是一个测试辅助函数。

如果您只对装饰器名称感兴趣,而不是潜在的装饰器参数,它会起作用。如果你想支持装饰器接受参数,像 line.strip().split()[0].split("(")[0] 这样的东西可以解决问题(未经测试)

最后,如果您愿意,可以通过将line.strip().split()[0] 替换为line.strip().split()[0][1:] 来删除“@”

【讨论】:

  • 像魅力一样工作!
【解决方案4】:

这是因为装饰器是“语法糖”。假设您有以下装饰器:

def MyDecorator(func):
    def transformed(*args):
        print "Calling func " + func.__name__
        func()
    return transformed

然后你把它应用到一个函数上:

@MyDecorator
def thisFunction():
    print "Hello!"

这相当于:

thisFunction = MyDecorator(thisFunction)

如果您可以控制装饰器,也许可以将“历史”嵌入到函数对象中。我敢打赌还有其他一些聪明的方法可以做到这一点(也许通过覆盖赋值),但不幸的是我对 Python 并不那么精通。 :(

【讨论】:

    【解决方案5】:

    作为Faisal notes,您可以让装饰器自己将元数据附加到函数中,但据我所知,这不会自动完成。

    【讨论】:

      【解决方案6】:

      在我看来这是不可能的。装饰器不是方法的某种属性或元数据。装饰器是一种用函数调用的结果替换函数的便捷语法。详情请见http://docs.python.org/whatsnew/2.4.html?highlight=decorators#pep-318-decorators-for-functions-and-methods

      【讨论】:

        【解决方案7】:

        这是不可能的,因为

        @foo
        def bar ...
        

        完全一样

        def bar ...
        bar = foo (bar)
        

        在某些特殊情况下,您可能会这样做,例如通过分析函数对象可能@staticmethod,但不会比这更好。

        【讨论】:

          【解决方案8】:

          你不能,但更糟糕的是,存在一些库来帮助隐藏你已经装饰了一个函数的事实。有关详细信息,请参阅 Functools 或装饰器库(@decorator,如果我能找到的话)。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2018-02-18
            • 2017-09-25
            • 2023-02-16
            • 1970-01-01
            • 2012-03-06
            • 2011-07-01
            • 2020-12-18
            • 2016-03-15
            相关资源
            最近更新 更多