【问题标题】:Why are python static/class method not callable?为什么 python 静态/类方法不可调用?
【发布时间】:2016-11-16 19:50:06
【问题描述】:

为什么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"))

结果是:

static_method is NOT callable
instance_method is  callable
class_method is NOT callable
class_var is NOT callable

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

我在学习python检查工具时遇到了这个。

来自 cmets 的补充说明:

cmets 中链接的 SO 答案说静态/类方法是 descriptors ,这是不可调用的。现在我很好奇,为什么描述符是不可调用的,因为描述符是具有特定属性(__get__、__set__、__del___ 之一)定义的类。

【问题讨论】:

标签: python oop methods


【解决方案1】:

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

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

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

def _this_is_not_stored_anywhere():
    pass
static_method = staticmethod(_this_is_not_stored_anywhere)

即,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"

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

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

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

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

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

【讨论】:

    猜你喜欢
    • 2015-11-13
    • 2011-03-02
    • 2013-08-20
    • 1970-01-01
    • 1970-01-01
    • 2012-07-20
    • 2011-12-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多