类内部装饰器与没有'self'的装饰类方法产生奇怪结果
示例代码:
# -*- coding: utf-8 -*-
from functools import wraps
class MyClass(object):
def __init__(self):
pass
#decorator inside class
def call(f):
@wraps(f)
def wrapper(*args):
print 'Wrapper: ', args
return wrapper
#decorated 'method' without self
@call
def myfunc(a):
pass
c = MyClass()
c.myfunc(1)
返回结果:
Wrapper: (<test3.MyClass object at 0xb788a34c>, 1)
这是正常的吗?有人能解释一下吗?
如果这是一个特性,我会在我的库中使用它。
2 个回答
@call
def myfunc(a):
...
def myfunc(a)
等同于
def myfunc(a):
...
myfunc=call(myfunc)
原来的 myfunc
可能只需要一个参数 a
,但是在被 call
装饰之后,新的 myfunc
可以接受任意数量的位置参数,所有这些参数都会被放到 args
里。
还要注意的是
def call(f)
从来没有调用过 f
。所以 myfunc
没有正常的 self 参数也不是问题,因为这个情况根本不会出现。
当你调用 c.myfunc(1)
时,wrapper(*args)
会被调用。
那么,什么是 args 呢?因为 c.myfunc
是一个方法调用,c
会作为第一个参数传入,后面跟着任何其他参数。在这个例子中,后面的参数是 1。这两个参数都会传给 wrapper
,所以 args
就是一个包含 (c,1)
的元组。
因此,你得到
Wrapper: (<test3.MyClass object at 0xb788a34c>, 1)
这完全是正常的现象。
函数 myfunc
被一个叫 wrapper
的实例替代了。wrapper
的参数格式是 (*args)
。因为它是一个绑定的方法,所以第一个参数是 MyClass
的实例,这个实例在字符串 `Wrapper: '` 后面被打印出来。
你有什么困惑的地方呢?
值得注意的是,如果你在 MyClass
外部使用 call
作为装饰器,会出现 TypeError
错误。解决这个问题的一种方法是给它加上 staticmethod
装饰器,但这样的话你就不能在类构造时调用它了。
这有点小技巧,但我在这里讲了如何两者兼得。
评论后的更新
无论你在参数列表中是否写 self
,它都会把实例作为第一个参数传入,因为在类创建后,实例被实例化时,它就是一个 绑定的方法。当你以这种形式调用它时
@instance.call
def foo(bar):
return bar + 1
它会展开成
def foo(bar):
return bar + 1
foo = instance.call(f)
但请注意,你是在一个实例上调用它!这会自动展开成这种形式的调用
def foo(bar):
return bar + 1
foo = MyClass.call(instance, f)
这就是方法的工作原理。但是你只定义了 call
接受一个参数,所以这会引发 TypeError
。
至于在类构造期间调用它,这样做是没问题的。但它返回的函数在被调用时会传入一个 MyClass
的实例,原因和我上面解释的一样。具体来说,无论你显式传入什么参数,它们都会在隐式和自动放置的实例参数之后。