在父类外部使用可调用对象装饰Python方法

3 投票
1 回答
1621 浏览
提问于 2025-04-17 12:36

假设我有一个类,想用它来做装饰器,像这样:

class specialmethod:
  def __init__(self,func):
     self.func = func

  def __call__(self,arg1,arg2):
     print arg1,arg2
     self.func(arg1,arg2)

但是,被装饰的方法是在另一个类里面,这个类也需要用到 self(因为 self 里面的值对装饰后的函数很重要)

class A:
  @specialmethod
  def dosomething(self,data,otherdata):
    #this guy down here isn't getting the right value for self, an instance of A
    print type(self)

Traceback (most recent call last):
  File "deco.py", line 18, in <module>
    a.dosomething('foo','bar')
  File "deco.py", line 7, in __call__
    self.func(arg1,arg2)
TypeError: dosomething() takes exactly 3 arguments (2 given)

有没有办法保持 self 的工作方式,就像没有被装饰一样?我试着显式地传递它,但还是期望的参数数量不对。

编辑

我需要澄清一下,最终的 dosomething 函数体中的 self 应该指向 A 的实例……我并不是想把装饰器的实例传到被装饰的函数里。

1 个回答

4

当你把一个类当作装饰器使用时,你需要明白,通常把函数变成方法的那种“魔法”(也就是描述符协议,特别是 __get__ 方法)在你的类上是不会自动生效的,除非你自己去实现它。你可以手动实现一个 __get__ 方法,这个方法会返回一个新的类型,这个类型会记住它是从哪个实例中获取的,以及实际要调用的函数,但这样做会很麻烦,而且收获不大。

更常见的做法是,不直接使用一个类作为装饰器,而是让装饰器返回一个真正的 Python 函数。在这种情况下,实际上根本没有必要保留这个类,所以你可以直接这样做:

def specialmethod(f):
    @functools.wraps(f)
    def wrapper(arg1, arg2):
        print arg1, arg2
        f(arg1, arg2)
    return wrapper

... 但是如果你真的想保留这个类,你可以这样做:

class _specialmethod:
  def __init__(self,func):
     self.func = func

  def __call__(self,arg1,arg2):
     print arg1,arg2
     self.func(arg1,arg2)

def specialmethod(f):
    wrapper_class = _specialmethod(f)
    @functools.wraps(f)
    def wrapper(arg1, arg2):
        wrapper_class(arg1, arg2)
    return wrapper

撰写回答