Python: 装饰器能否判断函数是否在类内定义?
我正在写一个装饰器,由于一些麻烦的原因[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
另外,请注意:
- 在应用装饰器的时候,函数仍然是一个普通的函数,而不是一个未绑定的方法,所以用
typeof
或inspect
来判断实例或未绑定的方法是行不通的。 - 请只提供能解决 这个 问题的建议——我知道有很多类似的方法可以实现这个目标(比如使用类装饰器),但我希望这些操作发生在 装饰 的时候,而不是之后。
[0]: 具体来说,我正在写一个装饰器,可以方便地进行参数化测试,使用 nose
。但是,nose
不会在 unittest.TestCase
的子类上运行测试生成器,所以我希望我的装饰器能够判断它是否在 TestCase
的子类中使用,并在出现问题时给出合适的错误提示。显而易见的解决方案是,在调用被包裹的函数之前使用 isinstance(self, TestCase)
,但这行不通,因为被包裹的函数 需要 是一个生成器,而这个生成器根本不会被执行。
6 个回答
我这里有一个比较简单的解决办法:
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
的参数。
我来得有点晚,不过这个方法被证明是一个可靠的方式,可以判断一个装饰器是否被用在类中定义的函数上:
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这样的工具中正常工作。
看看当你用装饰器包裹一个方法时,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
,但我找不到任何方法来了解类创建完成后会附加哪些超类。