Python 装饰器作为静态方法

51 投票
4 回答
29963 浏览
提问于 2025-04-16 19:54

我正在尝试写一个Python类,这个类里有一个装饰器函数需要用到实例的状态信息。这个功能按预期工作,但如果我把装饰器明确地设置为静态方法,就会出现以下错误:

Traceback (most recent call last):
  File "tford.py", line 1, in <module>
    class TFord(object):
  File "tford.py", line 14, in TFord
    @ensure_black
TypeError: 'staticmethod' object is not callable

这是为什么呢?

下面是代码:

class TFord(object):
    def __init__(self, color):
        self.color = color

    @staticmethod
    def ensure_black(func):
        def _aux(self, *args, **kwargs):
            if self.color == 'black':
                return func(*args, **kwargs)
            else:
                return None
        return _aux

    @ensure_black
    def get():
        return 'Here is your shiny new T-Ford'

if __name__ == '__main__':
    ford_red = TFord('red')
    ford_black = TFord('black')

    print ford_red.get()
    print ford_black.get()

如果我把@staticmethod这一行去掉,所有功能就都正常了,但我不明白为什么。难道不应该把self作为第一个参数吗?

4 个回答

4

解决方案是存在的!

问题在于,想要用作装饰器的静态方法实际上是一个静态方法对象,而不是可以直接调用的函数。

解决方案:静态方法对象有一个叫做 __get__ 的方法,它可以接收任何参数并返回真正可以调用的方法。详细信息可以查看Python 文档,适用于 Python 3.5 及以上版本:

class StaticMethod(object):
    "Emulate PyStaticMethod_Type() in Objects/funcobject.c"

    def __init__(self, f):
        self.f = f

    def __get__(self, obj, objtype=None):
        return self.f

我想到的最简单的解决方案是:

class A():
    def __init__(self):
        self.n =  2

    @staticmethod
    def _returnBaseAndResult(func):
        from functools import wraps
        @wraps(func)
        def wrapper(*args, **kwargs):
            self = args[0]
            response = func(*args, **kwargs)
            return self.n, response
        return wrapper

    @_returnBaseAndResult.__get__('this can be anything')
    def square(self):
        return self.n**2

if __name__ == '__main__':
    a = A()
    print(a.square())

将会打印出 (2, 4)

11

Python中的类是在运行时创建的,也就是在解释类的内容之后才会生成这个类。创建类的过程是把所有声明的变量和函数放到一个特别的字典里,然后用这个字典来调用 type.__new__(你可以查看自定义类创建的相关内容)。

所以,

class A(B):
    c = 1

其实是等同于:

A = type.__new__("A", (B,), {"c": 1})

当你用 @staticmethod 给一个方法加注解时,会发生一些特别的事情,这些事情是在用 type.__new__ 创建类之后发生的。在类的声明范围内,@staticmethod 这个功能其实只是一个静态方法对象的实例,你是不能直接调用它的。通常情况下,装饰器应该放在类定义的上方,或者放在一个单独的“装饰”模块里(这取决于你有多少个装饰器)。一般来说,装饰器应该放在类外面。一个特别的例外是属性类(你可以查看属性的相关内容)。在你的例子中,如果你有一个颜色类,可能把装饰器放在类声明里面是有意义的:

class Color(object):

    def ___init__(self, color):
        self.color = color

     def ensure_same_color(f):
         ...

black = Color("black")

class TFord(object):
    def __init__(self, color):
        self.color = color

    @black.ensure_same_color
    def get():
        return 'Here is your shiny new T-Ford'
47

这不是staticmethod应该使用的方式。staticmethod对象是一些特殊的东西,叫做描述符,它们返回的是被包装的对象,所以只有在用classname.staticmethodname这种方式访问时才有效。举个例子:

class A(object):
    @staticmethod
    def f():
        pass
print A.f
print A.__dict__["f"]

会打印出

<function f at 0x8af45dc>
<staticmethod object at 0x8aa6a94>

A的范围内,你总是会得到后者的对象,而这个对象是不能被调用的。

我强烈建议把装饰器放到模块的范围内——它似乎不应该放在类里面。如果你想把它放在类里面,那就不要用staticmethod,而是直接在类的末尾用del把它删除——在这种情况下,它不应该从类外部使用。

撰写回答