类内部装饰器与没有'self'的装饰类方法产生奇怪结果

1 投票
2 回答
2510 浏览
提问于 2025-04-16 04:40

示例代码:

# -*- 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 个回答

1
@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)
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 的实例,原因和我上面解释的一样。具体来说,无论你显式传入什么参数,它们都会在隐式和自动放置的实例参数之后。

撰写回答