Python: 装饰器能否判断函数是否在类内定义?

15 投票
6 回答
4620 浏览
提问于 2025-04-17 09:52

我正在写一个装饰器,由于一些麻烦的原因[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),但这行不通,因为被包裹的函数 需要 是一个生成器,而这个生成器根本不会被执行。

6 个回答

1

我这里有一个比较简单的解决办法:

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

我来得有点晚,不过这个方法被证明是一个可靠的方式,可以判断一个装饰器是否被用在类中定义的函数上:

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这样的工具中正常工作。

15

看看当你用装饰器包裹一个方法时,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,但我找不到任何方法来了解类创建完成后会附加哪些超类。

撰写回答