为什么python静态/类方法不可调用?

Dur*_*tta 8 python oop methods

为什么python实例方法可以调用,但静态方法和类方法不可调用?

我做了以下事情:

class Test():
    class_var = 42

    @classmethod 
    def class_method(cls):
        pass 

    @staticmethod
    def static_method():
        pass

    def instance_method(self):
        pass 

for attr, val in vars(Test).items():
    if not attr.startswith("__"):
        print (attr, "is %s callable" % ("" if callable(val) else "NOT"))
Run Code Online (Sandbox Code Playgroud)

结果是:

static_method is NOT callable
instance_method is  callable
class_method is NOT callable
class_var is NOT callable
Run Code Online (Sandbox Code Playgroud)

从技术上讲,这可能是因为实例方法对象可能具有以特定方式(可能__call__)设置的特定属性(非).为什么会出现这种不对称,或者它的用途是什么?

我在学习python检查工具时遇到过这个问题.

评论的其他评论:

注释中链接的SO答案说静态/类方法是描述符,它们是不可调用的.现在我很好奇,你为什么做出描述符不赎回,因为描述符类与特定的属性(之一__get__,__set__,__del___)定义.

mat*_*cik 7

为什么描述符不可调用?基本上是因为它们不需要。也不是每个描述符都代表一个可调用对象。

正如您正确地注意到的,描述符协议由__get__,__set__和组成__del__。注意不__call__,这是它不可调用的技术原因。实际可调用的是您的static_method.__get__(...).

至于哲学上的原因,我们来看看类。的内容__dict__,或者在您的情况下的结果vars(),基本上locals()class块的内容。如果你def是一个函数,它会被转储为一个普通的函数。如果您使用装饰器,例如@staticmethod,则相当于:

def _this_is_not_stored_anywhere():
    pass
static_method = staticmethod(_this_is_not_stored_anywhere)
Run Code Online (Sandbox Code Playgroud)

即,static_method被分配了staticmethod()函数的返回值。

现在,函数对象实际上实现了描述符协议——每个函数都有一个__get__方法。这就是特殊self和绑定方法行为的来源。看:

def xyz(what):
    print(what)

repr(xyz)  # '<function xyz at 0x7f8f924bdea0>'
repr(xyz.__get__("hello"))  # "<bound method str.xyz of 'hello'>"
xyz.__get__("hello")()  # "hello"
Run Code Online (Sandbox Code Playgroud)

由于类调用的方式__get__,您test.instance_method绑定到实例并将其预填充为第一个参数。

但是@classmethodand的全部意义@staticmethod在于他们做了一些特别的事情来避免默认的“绑定方法”行为!所以他们不能返回一个普通的函数。相反,它们返回一个带有自定义__get__实现的描述符对象。

当然,你可以__call__在这个描述符对象上放置一个方法,但为什么呢?这是您在实践中不需要的代码;您几乎永远无法触及描述符对象本身。如果您这样做(在与您的代码类似的代码中),您仍然需要对描述符进行特殊处理,因为通用描述符不必(具有类似)可调用 - 属性也是描述符。所以你不想__call__在描述符协议中。因此,如果第三方“忘记”__call__在您认为“可调用”的东西上实现,您的代码将错过它。

此外,该对象是一个描述符,而不是一个函数。把__call__方法放在上面会掩盖它的真实本质:) 我的意思是,它本身并没有,它只是......你永远不需要的东西。

顺便说一句,在 classmethod/staticmethod 的情况下,您可以从它们的__func__属性中取回原始函数。