【问题标题】:Python: can a decorator determine if a function is being defined inside a class?Python:装饰器能否确定一个函数是否在类中定义?
【发布时间】:2012-02-06 06:31:36
【问题描述】:

我正在编写一个装饰器,由于各种烦人的原因[0],检查它所包装的函数是独立定义的还是作为类的一部分定义的(以及新类的哪些类是子类化)。

例如:

def my_decorator(f):
    defined_in_class = ??
    print "%r: %s" %(f, defined_in_class)

@my_decorator
def foo(): pass

class Bar(object):
    @my_decorator
    def bar(self): pass

应该打印:

<function foo …>: False
<function bar …>: True

另外,请注意:

  • 在应用装饰器时,函数仍然是函数,而不是未绑定的方法,因此测试实例/未绑定方法(使用 typeofinspect)将不起作用。
  • 请只提供解决这个问题的建议——我知道有很多类似的方法可以达到这个目的(例如,使用类装饰器),但我希望它们发生在装修时间,不迟。

[0]:具体来说,我正在编写一个装饰器,它可以很容易地使用nose 进行参数化测试。但是,nose不会unittest.TestCase 的子类上运行测试生成器,所以我希望我的装饰器能够确定它是否在 TestCase 的子类中使用并失败并显示适当的错误。显而易见的解决方案 - 在调用包装函数之前使用 isinstance(self, TestCase) 不起作用,因为包装函数 需要 是一个生成器,它根本不会被执行 .

【问题讨论】:

标签: python inspect


【解决方案1】:

我认为inspect 模块中的功能可以满足您的需求,尤其是isfunctionismethod

>>> import inspect
>>> def foo(): pass
... 
>>> inspect.isfunction(foo)
True
>>> inspect.ismethod(foo)
False
>>> class C(object):
...     def foo(self):
...             pass
... 
>>> inspect.isfunction(C.foo)
False
>>> inspect.ismethod(C.foo)
True
>>> inspect.isfunction(C().foo)
False
>>> inspect.ismethod(C().foo)
True

然后您可以按照Types and Members table 访问绑定或未绑定方法内的函数:

>>> C.foo.im_func
<function foo at 0x1062dfaa0>
>>> inspect.isfunction(C.foo.im_func)
True
>>> inspect.ismethod(C.foo.im_func)
False

【讨论】:

  • 我希望inspect 会参与其中,但不是您描述的方式。请参阅我的第一个注释(在应用装饰器时,该函数只是function 的一个实例,而不是unboundmethodboundmethod)。
  • 他没有明确说明,但他的意思是这在类定义时不起作用。
  • 是的,你是对的——我已经更新了我的问题,使其更加明确。我很欣赏这个彻底的答案,它只是没有解决我的问题。
【解决方案2】:

我有一些 hacky 解决方案:

import inspect

def my_decorator(f):
    args = inspect.getargspec(f).args
    defined_in_class = bool(args and args[0] == 'self')
    print "%r: %s" %(f, defined_in_class)

但它依赖于函数中 self 参数的存在。

【讨论】:

  • 这保证会因误报和否定而失败。 self 只是一个临时社区标准。没有强制执行self,因此不应该期望在未绑定的方法和第一个参数巧合地命名为self 的可调用对象之间存在一对一的关系。
【解决方案3】:

在包装方法时查看inspect.stack() 的输出。当您的装饰器正在执行时,当前堆栈帧是对您的装饰器的函数调用;下一个堆栈帧是应用于新方法的@ 包装操作;第三帧将是类定义本身,它值得一个单独的堆栈帧,因为类定义是它自己的命名空间(它在执行完成时被包装起来创建一个类)。

因此我建议:

defined_in_class = (len(frames) > 2 and
                    frames[2][4][0].strip().startswith('class '))

如果所有这些疯狂的索引看起来都无法维护,那么您可以通过将帧逐个拆开来更加明确,如下所示:

import inspect
frames = inspect.stack()
defined_in_class = False
if len(frames) > 2:
    maybe_class_frame = frames[2]
    statement_list = maybe_class_frame[4]
    first_statment = statement_list[0]
    if first_statment.strip().startswith('class '):
        defined_in_class = True

请注意,我确实没有看到在包装器运行时向 Python 询问类名或继承层次结构的任何方法;这一点在处理步骤中“为时过早”,因为类创建尚未完成。要么自己解析以class 开头的行,然后在该框架的全局变量中查找超类,要么在frames[1] 代码对象周围查看你可以学到什么——看起来类名最终是@ 987654327@ 在上面的代码中,但我找不到任何方法来了解当类创建完成时将附加哪些超类。

【讨论】:

    【解决方案4】:

    您可以检查装饰器本身是在模块级别调用还是嵌套在其他东西中。

    defined_in_class = inspect.currentframe().f_back.f_code.co_name != "<module>"
    

    【讨论】:

    • Hrm...但这不适用于嵌套在某些东西中的类,不是吗? (例如,def mk_class(): class MyClass: … ; return MyClass
    • 函数所做或返回的内容不影响inspect.currentframe().f_back的值;唯一重要的是调用my_decorator 的位置,并且它始终与定义它所环绕的事物的级别相同。
    • 我可能误解了你的评论;在调用封闭函数之前,其他函数中的任何装饰函数都不会运行装饰器,但包装函数的范围将被正确计算。您可以在装饰器中使用它来查看运行装饰器时的封闭范围列表:[x[0].f_code.co_name for x in inspect.stack()[1:]].
    • 这保证会因误报而失败, 特别是对于嵌套闭包(即在其他函数中声明的函数在其他函数中声明)。嵌套闭包是可装饰的,但其父闭包的 f_code.co_name 属性保证是有效的 Python 标识符,因此 不是 &lt;module&gt;
    【解决方案5】:

    在这里聚会有点晚了,但这已被证明是确定装饰器是否用于类中定义的函数的可靠方法:

    frames = inspect.stack()
    
    className = None
    for frame in frames[1:]:
        if frame[3] == "<module>":
            # At module level, go no further
            break
        elif '__module__' in frame[0].f_code.co_names:
            className = frame[0].f_code.co_name
            break
    

    与公认答案相比,此方法的优势在于它适用于例如py2exe。

    【讨论】:

    • 这在冻结的应用程序(例如,PyInstaller、py2exe)上很可能成功,但这在嵌套类的未冻结应用程序上肯定会失败——这是一个更常见的用例。 '__module__'不会在嵌套类的 frame[0].f_code.co_names 元组中,尤其是嵌套类本身本地化为嵌套闭包。装饰作者在这里无法休息。
    【解决方案6】:

    你可以使用包wrapt来检查
    - 实例/类方法
    - 课程
    - 独立函数/静态方法:

    查看wrapt的项目页面:https://pypi.org/project/wrapt/

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-04-24
      • 2018-01-12
      • 2015-10-23
      • 1970-01-01
      • 2021-04-19
      • 1970-01-01
      • 2015-12-06
      相关资源
      最近更新 更多