TL;DR 任何实现__call__ 的对象都可以被调用,例如:函数、自定义类等。
略长的版本:(walloftext)
关于你的问题的完整答案是在 python 虚拟机的实现中,所以我们必须看看 python 的底层。首先是代码对象的概念。 Python 将你扔给它的任何东西解析成它自己的内部语言,这种语言在所有平台上都是相同的,称为字节码。一个非常直观的表示是当您在导入您编写的自定义库后获得一个 .pyc 文件时。这些是 python VM 的原始指令。忽略这些指令是如何从您的源代码创建的,它们随后由 Python/ceval.c 中的PyEval_EvalFrameEx 执行。源代码有点像野兽,但最终像一个简单的处理器一样工作,其中一些复杂的部分被抽象掉了。字节码是该处理器的汇编语言。特别是这个“处理器”的“操作码”之一是(恰当地命名)CALL_FUNCTION。回调经过多次调用,最终到达PyObject_Call()。该函数接受一个指向PyObject 的指针,并从它的类型中提取tp_call 属性并直接调用它(从技术上讲,它首先检查它是否存在):
...
call = func->ob_type->tp_call //func is an arg of PyObject_Call() and is a pointer to a PyObject
...
result = (*call)(func, arg, kw);
任何实现__call__ 的对象都会被赋予一个tp_call 属性和一个指向实际函数的指针。我相信这是由 Objects/typeobject.c 中的slotdefs[] difinition 处理的:
FLSLOT("__call__", tp_call, slot_tp_call, (wrapperfunc)wrap_call,
"__call__($self, /, *args, **kwargs)\n--\n\nCall self as a function.",
PyWrapperFlag_KEYWORDS)
函数的__call__ 方法本身在cpython 实现中定义,它定义了python VM 应如何开始执行该函数的字节码以及应如何返回数据(如果有)。当您给任意类一个__call__ 方法时,该属性是一个函数对象,它再次引用__call__ 的cpython 实现。因此,当您调用“正常”函数时,会引用 foo.__call__。当您调用可调用类时,self.__call__ 等效于 foo,而实际调用的 cpython 引用是 self.__call__.im_func.__call__。
免责声明
这对我来说是一段进入未知领域的旅程,我完全有可能歪曲了实施的一些细节。我主要从 this 博客文章中获取关于 python 可调用对象如何在后台工作的文章,以及我自己通过 python source code 进行的一些挖掘