我正在尝试创建一个类,该类被赋予一个函数,然后从该实例运行。但是,当我尝试使用staticmethod
时,我发现使用装饰器和仅传递函数staticmethod
是有区别的。
class WithDec():
def __init__(self):
pass
@staticmethod
def stat(val):
return val + 1
def OuterStat(val):
return val + 1
class WithoutDec():
def __init__(self, stat):
self.stat = staticmethod(stat)
对于这两个类,将发生以下情况。
>>> WithDec().stat(2)
3
>>> WithoutDec(OuterStat).stat(2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'staticmethod' object is not callable
发生了什么,我能做些什么来阻止它。
静态方法仍然通过描述符协议工作,这意味着当它是类属性时,通过实例访问它仍然意味着将调用__get__
方法以返回实际调用的对象。那是
WithDec().stat(2)
相当于
w = WithDec()
w.stat(2)
相当于
WithDec.stat.__get__(w, WithDec)(2)
但是,当静态方法是实例属性时,不会调用描述符协议,就像WithoutDec
一样。在这种情况下
WithoutDec().stat(2)
尝试调用文字staticmethod
实例stat
,而不是stat.__get__
返回的函数。
你想要的是使用staticmethod
来创建类属性,而不是通过装饰器语法:
class WithoutDec():
def stat(val):
return val + 1
stat = staticmethod(stat)
首先将stat
绑定到常规函数(在尝试将其用作实例方法之前,它不是真正的实例方法),然后将该函数替换为包装原始函数的staticmethod
实例。
问题是您正在尝试在__init__
中使用staticmethod()
,它用于创建类的实例,而不是直接在类级别定义类、其方法和静态方法。
此代码有效:
def OuterStat(val):
return val + 1
class WithoutDec():
stat = staticmethod(OuterStat)
>>> WithoutDec.stat(2)
3
请注意,尝试使用自己的不同 stat 版本创建WithoutDec
实例与静态方法的含义相反。
我在这个线程上找到了一个非常鼓舞人心的解决方案。事实上,你的代码不是很pythonic,并且将静态方法归因于类实例的属性。以下代码有效:
class WithoutDec():
stat = None
@staticmethod
def OuterStat(val):
return val + 1
然后你打电话:
my_without_dec = WithoutDec()
my_without_dec.stat = WithotuDec.OuterStat
my_without_dec.stat(2)
稍后,如果要创建新方法,请调用:
def new_func(val):
return val+1
WithoutDec.newStat = staticmethod(new_func)
my_without_dec.stat = WithoutDec.newStat
my_without_dec.stat(2)
是的- 在这种情况下,您只需将函数添加为实例的属性,它将按预期工作,不需要任何装饰器:
def OuterStat(val):
return val + 1
class WithoutDec():
def __init__(self, stat):
self.stat = stat
问题是:如果函数是类的属性还是实例的属性,这是有区别的。当它在带有self.func = X
的实例方法中设置时,它成为一个实例属性 - Python 以存储方式检索它,没有修改,它只是对可以调用的原始函数的另一个引用。
相反,当函数存储为类属性时,默认行为是将其用作实例方法:从实例检索函数时,Python 会安排内容,以便self
作为该函数的第一个参数注入。在这种情况下,装饰器@classmethod
存在,@staticmethod
修改此行为(对类进行classmethod
或staticmethod
不进行注入)。
问题是staticmethod
不返回函数 - 它返回一个用作类属性的描述符,因此当从类中检索修饰的函数时,它作为一个普通函数工作。
(内部细节:所有 3 种行为:实例方法、类方法和静态方法都是通过在用作类属性的对象上使用适当的__get__
方法来实现的)。
注意:有一些关于使"staticmethod"成为"可调用"的讨论,并简单地调用包装的函数 - 我刚刚检查了它是否进入了Pythonn 3.10 beta 1。这意味着您的示例代码将像 Python 3.10 一样工作 - 尽管如此,正如本答案开头所述,那里的 staticmethod 调用是多余的,不应使用。