【问题标题】:How do Python tell “this is called as a function”?Python如何告诉“这被称为函数”?
【发布时间】:2014-09-10 17:22:20
【问题描述】:

通过定义__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__ 多说……仅此而已:

3.3.5。模拟可调用对象

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

当实例作为函数“调用”时调用; […]

但是 Python 如何判断“它被称为函数”以及是否遵守 object.__call__ 规则?

这可能与类型有关,但即使是类型也有 object 作为其基类。

我在哪里可以了解更多(正式)相关信息?

顺便问一下,这里Python2和Python3有什么区别吗?

----- %% -----

一答一评论后的结论及其他实验

更新 #1

在@Veedrac 的回答和@chepner 的评论之后,我来到了另一个测试,它完成了两者的 cmets:

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__ 用来作为函数调用,不是来自对象,而是来自它的类型。

【问题讨论】:

  • A.__call__() 按您演示的方式工作。它不会打印In __call__
  • 这至少是它对 Python 3.4 所做的事情(您使用的是较旧的 Python 版本吗?)
  • Instances of A 是可调用的;根据对象的类型查找特殊方法;例如,您不能将 __call__ 设为实例的属性。
  • 只是因为您使用*args 进行签名。打印出args 是什么,你会看到那里的不同。您正在那里访问 函数对象
  • 你知道吗?函数是可调用的。当你直接打电话给他们时,你给他们起什么名字并不重要。

标签: python


【解决方案1】:

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

所以你有

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

这一切都说得通。


chepner 在 cmets 中指出 type(A) == type。这有点奇怪,因为type(A)(A) 又给了type!但请记住,我们使用的是type(A).__call__(A),这不一样

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


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

【讨论】:

  • 可能值得明确指出 type(A)type
  • 那是因为它有type 作为祖先?这意味着这不仅仅是定义__call__,它使某些东西可以调用并且涉及更多。
  • 不,因为type 本身就是一种类型,所以当您尝试将typeinstance 作为函数调用时,就会调用type.__call__。由于类Atype 的一个实例,所以type.__call__ 是在您编写A() 时调用的(具体来说,它调用A.__new__)。没有使类可调用的特殊例外;这只是类成为其元类实例的结果。
  • 要记住的另一件事是A (type) 的元类不同于A (object) 的父类。 (由于objecttype 的父类,typeobject 的元类,而type 没有元类,object 没有父类,这有点有点混淆. 也许最好忽略这个括号。)
  • Annnnd.... 最后一条评论。如果f 是一个函数,那么f() 也不特殊。它只是type(f).__call__(f) 的缩写。没有引用该类型的内置名称,但types 模块中有一个:import types; types.FunctionType.__call__(f)
猜你喜欢
  • 1970-01-01
  • 2018-04-06
  • 2011-08-29
  • 2019-01-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-01
相关资源
最近更新 更多