Python:装饰器可以确定是否在类中定义了函数?

时间:2012-01-09 18:24:12

标签: python inspect

我正在编写一个装饰器,并且由于各种恼人的原因[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进行参数化测试。但是,noseunittest.TestCase的子类上运行测试生成器,所以我希望我的装饰器能够确定它是否在{{1}的子类中使用并且失败并出现适当的错误。显而易见的解决方案 - 在调用包装函数之前使用TestCase不起作用,因为包装函数需要成为一个生成器,它根本不会被执行

6 个答案:

答案 0 :(得分:13)

在包装方法时,请查看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]代码对象周围查看你能学到什么 - 看来类名在上面的代码中结束了frames[1][0].f_code.co_name,但我找不到任何方法来学习在类创建完成时将附加哪些超类。

答案 1 :(得分: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参数。

答案 2 :(得分:2)

这里的派对有点晚了,但事实证明这是确定装饰器是否用于类中定义的函数的可靠方法:

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。

答案 3 :(得分:1)

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

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

答案 4 :(得分:1)

您可以使用软件包wrapt进行检查
 -实例/类方法
 -课程
 -独立的函数/静态方法:

请参见wrapt的项目页面:https://pypi.org/project/wrapt/

答案 5 :(得分:-2)

我认为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
相关问题