Python如何告诉“这被称为函数”?

时间:2014-09-10 17:22:20

标签: python

通过定义__call__,可以调用可调用对象。一个类应该是一个对象......或者至少有一些例外。这个例外是我未能正式澄清的,因此这个问题发布在这里。

A成为一个简单的类:

class A(object):

    def call(*args):
        return "In `call`"

    def __call__(*args):
        return "In `__call__`"

第一个功能是故意命名为“call”,以明确目的是与另一个进行比较。

让我们实例化它并忘记它暗示的表达式:

a = A() # Think of it as `a = magic` and forget about `A()`

现在值得:

print(A.call())
print(a.call())
print(A())
print(a())

结果:

>>> In `call`
>>> In `call`
>>> <__main__.A object at 0xNNNNNNNN>
>>> In `__call__`

输出(第三个语句没有运行__call__)并不令人感到意外,但是当我认为每个地方都说“Python类是对象”时......

这更明确,但运行__call__

print(A.__call__())
print(a.__call__())

>>> “In `__call__`”
>>> “In `__call__`”

所有这一切只是为了表明最终A()看起来有多奇怪。

Python规则中有例外,但the documentation about “object.call__call__没有太多说明......不超过:{/ p>

  

3.3.5。模拟可调用对象

object.__call__(self[, args...])
     

当实例被“调用”为函数时调用; [...]

但是,Python如何告诉“它被称为函数”并尊重或不尊重object.__call__规则?

这可能是一个类型问题,但即使是类型也有object作为基类。

我在哪里可以了解更多(并且正式)有关它的信息?

顺便问一下,Python2和Python3之间有什么不同吗?

-----%&lt; -----编辑-----&gt;%-----

一个答案和一个评论之后的结论和其他实验

更新#1

在@ Veedrac的回答和@Chepner的评论之后,我进入了另一个测试,完成了两者的评论:

class M(type):

    def __call__(*args):
        return "In `M.__call__`"

class A(object, metaclass=M):

    def call(*args):
        return "In `call`"

    def __call__(*args):
        return "In `A.__call__`"

print(A())

结果是:

>>> In `M.__call__`

所以似乎是驱动“呼叫”操作的元类。如果我理解正确的话,元类只与类有关,也与类实例无关。

更新#2

另一个相关测试,它表明这不是重要对象的属性,而是对象类型的属性:

class A(object):

    def __call__(*args):
        return "In `A.__call__`"

def call2(*args):
    return "In `call2`"


a = A()

print(a())

正如所料,它打印:

>>> In `A.__call__`

现在这个:

a.__call__ = call2

print(a())

打印:

>>> In `A.__call__`

在分配属性之前的相同a。它不会打印In call2,它仍然是In A.__call__。这一点很重要,也可以解释为什么这是调用的元类的__call__(请记住,元类是类对象的类型)。用于调用函数的__call__不是来自对象,而是来自其类型。

1 个答案:

答案 0 :(得分:5)

x(*args, **kwargs)type(x).__call__(x, *args, **kwargs)相同。

所以你有

>>> type(A).__call__(A)
<__main__.A object at 0x7f4d88245b50>

这一切都有道理。


chepner在type(A) == type的评论中指出。这有点奇怪,因为type(A)(A)只会再次发出type!但请记住,我们使用的是type(A).__call__(A) 不一样

所以这解析为type.__call__(A)。这是类的构造函数,它构建数据结构并完成所有构造魔法。


大多数dunder(双下划线)方法也是如此,例如__eq__。在这些情况下,这部分是优化。