我正在学习Python 3.X中的重载,为了更好地理解这个主题,我写了下面的代码,它在Python 3.X中可以工作,但在Python 2.X中不能。我以为下面的代码会失败,因为我没有为类Test
定义__call__
。但令我惊讶的是,它可以工作,并打印"constructor called"
.Demo。
class Test:
def __init__(self):
print("constructor called")
#Test.__getitem__() #error as expected
Test.__call__() #this works in 3.X(but not in 2.X) and prints "constructor called"! WHY THIS DOESN'T GIVE ERROR in 3.x?
所以我的问题是,这段代码究竟是如何/为什么在3.x中工作的,而在2.x中却不行。我的意思是,我想知道背后的机制是什么。
更重要的是,当我使用__call__
时,为什么这里使用__init__
?
2条答案
按热度按时间n3schb8v1#
在3.x中:
关于属性查找,
type
和object
每次在对象上查找属性时,Python都遵循如下过程:
1.它是否直接是对象中实际数据的一部分?如果是,使用它并停止。
1.它直接是对象类的一部分吗?如果是,请在步骤4中继续。
1.否则,检查对象的类是否有
__getattr__
和__getattribute__
覆盖,查看MRO中的基类,等等(当然,这是一个巨大的简化)。1.如果在第2步或第3步中找到了check if it has a
__get__
,如果找到了,就查找它(是的,这意味着从第1步开始查找该对象上名为__get__
的属性),调用它,并使用它的返回值,否则,直接使用返回的值。函数有一个
__get__
自动;用于实现方法绑定,类是对象;这就是为什么可以在它们里面查找属性。也就是说:class Test:
块的用途是定义数据类型;代码创建名为Test
的对象,该对象表示定义的数据类型。但是由于
Test
类是一个对象,它必须是某个类的示例,这个类叫做type
,有一个内置的实现。注意,
type(Test)
不是一个函数调用,而是预定义了名称type
来引用一个类,在用户代码中创建的每个其他类(默认情况下)都是这个类的示例。换句话说,
type
是默认的元类:阶级的阶级。有人可能会问,
type
属于什么类?答案出奇地简单--本身:由于上面的例子调用了
type
,我们得出结论,type
是可调用的。为了是可调用的,它必须有一个__call__
属性,它做到了:当
type
用一个参数调用时,它查找参数的类(大致相当于访问参数的__class__
属性);当用三个参数调用时,它创建type
的一个新示例,即一个新类。type
是如何工作的?因为这是在挖掘语言的核心(为对象分配内存),所以不太可能在纯Python中实现它,至少对于参考C实现是这样(我不知道PyPy在这里有什么神奇之处),但是我们可以 * 近似地 * 像这样建模
type
类:当我们调用类(
Test()
)时(概念上)会发生什么?Test()
使用函数调用语法,但它不是一个函数。为了弄清楚应该发生什么,我们将调用转换为Test.__class__.__call__(Test)
。(我们在这里直接使用__class__
,因为使用type
转换函数调用-要求type
对自己进行分类-将以无限递归结束。)1.就是
type
,所以这个就变成了type.__call__(Test)
。type
直接包含一个__call__
(type
是它自己的类,记得吗?),所以它是直接使用的--我们不通过__get__
描述符,我们调用函数,把Test
作为self
,没有其他参数。(我们现在有了一个函数,所以不需要再翻译函数调用语法,我们 * 可以 * -给定一个函数func
,func.__class__.__call__.__get__(func)
给我们一个未命名的内置“方法 Package 器”类型的示例,它在被调用时做的事情与func
相同。在方法 Package 器上重复循环会创建一个单独的方法 Package 器,它仍然做同样的事情。)1.这将尝试调用
Test.__new__(Test)
(因为self
绑定到Test
)。Test.__new__
没有在Test
中显式定义,但因为Test
是一个类,所以我们不会在Test
的类(type
)中查找,而是在Test
的基(object
)中查找。object.__new__(Test)
已经存在,并且它有神奇的内置功能,可以为Test
类的新示例分配内存,可以为该示例分配属性(即使Test
是object
的子类型,这是不允许的),并将其__class__
设置为Test
。类似地,当我们调用
type
时,相同的逻辑链将type(Test)
转换为type.__class__.__call__(type, Test)
,再转换为type.__call__(type, Test)
,然后转发到type.__new__(type, Test)
。这次,type
中直接有一个__new__
属性,因此这不会退回到查找object
。当name_or_obj
被设置为Test
时,我们简单地返回Test.__class__
,即type
,并且使用单独的name, bases, attrs
参数,type.__new__
创建type
的示例。最后:当我们显式调用
Test.__call__()
时会发生什么?如果类中定义了一个
__call__
,则会使用它,因为它是直接找到的,但这会失败,因为没有足够的参数:描述符协议没有被使用,因为属性是直接找到的,所以self
没有被绑定,所以参数丢失。如果没有定义
__call__
方法,那么我们就在Test
的类中查找,也就是type
,那里有一个__call__
,所以剩下的就像上一节中的步骤3-5一样。vq8itlhq2#
在Python 3.x中,每个类都隐含地是内置类
object
的一个子类,至少在CPython实现中,object
类有一个__call__
方法,该方法在它的元类type
中定义。这意味着
Test.__call__()
与Test()
完全相同,并且将返回一个新的Test
对象,调用您的自定义__init__
方法。在Python 2.x中,类默认是 * old style * 类,并且不是
object
的子类,因此__call__
没有定义,在Python 2.x中,你可以通过使用 new style 类来获得相同的行为,也就是说,在object上进行显式继承: