python 中复杂多重继承中的构造函数调用序列
Sequence of constructor calls in complex multiple inheritance in python
我对陈述有疑问
name = SizedRegexString(maxlen=8, pat='[A-Z]+$')
在下面的代码中。我无法理解 init 调用是如何在层次结构中发生的。
# Example of defining descriptors to customize attribute access.
from inspect import Parameter, Signature
import re
from collections import OrderedDict
class Descriptor:
def __init__(self, name=None):
print("inside desc")
self.name = name
def __set__(self, instance, value):
instance.__dict__[self.name] = value
def __delete__(self, instance):
raise AttributeError("Can't delete")
class Typed(Descriptor):
ty = object
def __set__(self, instance, value):
if not isinstance(value, self.ty):
raise TypeError('Expected %s' % self.ty)
super().__set__(instance, value)
class String(Typed):
ty = str
# Length checking
class Sized(Descriptor):
def __init__(self, *args, maxlen, **kwargs):
print("inside sized")
self.maxlen = maxlen
super().__init__(*args, **kwargs)
def __set__(self, instance, value):
if len(value) > self.maxlen:
raise ValueError('Too big')
super().__set__(instance, value)
class SizedString(String, Sized):
pass
# Pattern matching
class Regex(Descriptor):
def __init__(self, *args, pat, **kwargs):
print("inside regex")
self.pat = re.compile(pat)
super().__init__(*args, **kwargs)
def __set__(self, instance, value):
if not self.pat.match(value):
raise ValueError('Invalid string')
super().__set__(instance, value)
class SizedRegexString(SizedString, Regex):
pass
# Structure definition code
def make_signature(names):
return Signature(
Parameter(name, Parameter.POSITIONAL_OR_KEYWORD)
for name in names)
class StructMeta(type):
@classmethod
def __prepare__(cls, name, bases):
return OrderedDict()
def __new__(cls, clsname, bases, clsdict):
fields = [key for key, val in clsdict.items()
if isinstance(val, Descriptor) ]
for name in fields:
clsdict[name].name = name
clsobj = super().__new__(cls, clsname, bases, dict(clsdict))
sig = make_signature(fields)
setattr(clsobj, '__signature__', sig)
return clsobj
class Structure(metaclass=StructMeta):
def __init__(self, *args, **kwargs):
bound = self.__signature__.bind(*args, **kwargs)
for name, val in bound.arguments.items():
setattr(self, name, val)
if __name__ == '__main__':
class Stock(Structure):
name = SizedRegexString(maxlen=8, pat='[A-Z]+$')
for item in SizedRegexString.__mro__:
print(item)
init 中打印语句的输出:
inside sized
inside regex
inside desc
inside desc
inside desc
SizedRegexString 的 mro 输出 class
<class '__main__.SizedRegexString'>
<class '__main__.SizedString'>
<class '__main__.String'>
<class '__main__.Typed'>
<class '__main__.Sized'>
<class '__main__.Regex'>
<class '__main__.Descriptor'>
<class 'object'>
init和set两个调用链是否都遵循mro?或者这里发生了其他事情?
我不清楚你的问题到底是什么,所以如果你能准确地解释你期望发生的事情,以及这与实际发生的事情有何不同,那将会很有帮助。鉴于这一事实,我将尝试在此处解释如何评估 MRO。
首先,由于示例代码中的class层次结构比较复杂,可能有助于可视化继承结构:
关于你的问题,
Does init and set both call chains follow the mro?
如果我没理解错的话,简短的回答是肯定的。 MRO是根据class继承决定的,是classes的属性,不是方法。你通过 SizedRegexString.__mro__
的循环说明了这个事实,所以我猜你的问题是由于 __init__
和 __set__
的调用链之间的感知差异引起的。
__init__ 调用链
SizedRegexString.__init__
的调用链如下:
SizedRegexString.__init__
,没有明确定义,所以遵从其超class的定义
SizedString.__init__
,未明确定义
String.__init__
,未明确定义
Typed.__init__
,未明确定义
Sized.__init__
,设置 maxlen
,然后调用 super().__init__()
Regex.__init__
,设置 pat
,然后调用 super().__init__()
Descriptor.__init__
,设置 name
所以在调用 SizedRegexString.__init__
时,根据 MRO,有七个定义的 classes 需要检查 __init__
方法(假设每个调用 super().__init__()
,还有)。但是,正如您所指出的,__init__
方法中的 print 语句的输出显示访问了以下 classes:Sized
、Regex
和 Descriptor
.请注意,这些 classes - 顺序相同 - 与上面项目符号中明确定义的那些相同。
因此,对我们来说,似乎 SizedRegexString
的 MRO 是 [Sized
、Regex
、Descriptor
] 因为这是我们看到的仅有的三个 classes 实际在做事。然而,这种情况并非如此。上面带项目符号的 MRO 仍然被遵守,但是 Sized
之前的 classes 的 none 明确定义了一个 __init__
方法,所以他们每个人都默默地遵守他们的 superclass是的。
__set__ 调用链
这解释了 __init__
如何遵循 MRO,但为什么 __set__
似乎表现不同?要回答这个问题,我们可以遵循上面使用的相同项目符号 MRO:
SizedRegexString.__set__
,没有明确定义,所以遵从它的超class的定义
SizedString.__set__
,未明确定义
String.__set__
,未明确定义
Typed.__set__
,检查 value
是 self.ty
的实例,然后调用 super().__set__()
Sized.__set__
,检查 value
的长度,然后调用 super().__set__()
Regex.__set__
,确保 self.pat
和 value
之间的匹配,然后调用 super().__set__()
Descriptor.__set__
,将 self.name
和 value
的 key/value 对添加到 instance.__dict__
这里的要点是 __set__
遵守与 __init__
相同的 MRO,因为它们属于相同的 class,即使我们从四个不同的地方看到 activity class 这次是,而我们只看到三个 __init__
。因此,再一次,看起来好像SizedRegexString
的MRO现在是[Typed
、Sized
、Regex
、Descriptor
]。这可能会造成混淆,因为这个新的调用链不同于 SizedRegexString.__mro__
和我们在 SizedRegexString.__init__
中看到的明显调用链。
TL;DR
但是在跟踪 __init__
和 __set__
的调用链之后,我们可以看到它们都遵循 MRO。差异来自于 Descriptor
的更多后代显式定义 __set__
方法而不是 __init__
方法。
加分
以下几点可能会引起一些混淆:
None 定义的 __set__
方法实际上在您的示例代码的当前状态下被调用。我们可以从您的示例代码中找出以下两行原因:
class Stock(Structure):
name = SizedRegexString(maxlen=8, pat=“[A-Z]+$”)
这两条线的最终产品 (Stock
) 是由 StructMeta
metaclass 的 __new__
方法生成的。虽然 Stock
确实具有 name
class 属性,它是一个 SizedRegexString
实例,但未设置此实例的任何属性。因此,调用了 __set__
个方法中的 none 个。我们期望 __set__
被调用的地方是在 Stock.__init__
中,因为 Structure.__init__
中的以下行:
for n, v in bound.arguments.items():
setattr(self, n, v)
通过在示例代码的末尾添加 s = Stock(name=“FOO”)
,我们可以看到 __set__
方法执行成功。此外,我们可以验证 Regex.__set__
和 Sized.__set__
以及 s = Stock(name=“foo”)
和 s = Stock(name=“FOOFOOFOO”)
分别引发了正确的错误
在Python 3.6之后,dict
默认是有序的,所以StructMeta
中的__prepare__
方法可能是多余的,具体取决于Python 您正在使用的版本
希望我回答了你的问题。如果我完全错过了要点,如果您能准确说明您的期望,我很乐意再试一次。
我对陈述有疑问
name = SizedRegexString(maxlen=8, pat='[A-Z]+$')
在下面的代码中。我无法理解 init 调用是如何在层次结构中发生的。
# Example of defining descriptors to customize attribute access.
from inspect import Parameter, Signature
import re
from collections import OrderedDict
class Descriptor:
def __init__(self, name=None):
print("inside desc")
self.name = name
def __set__(self, instance, value):
instance.__dict__[self.name] = value
def __delete__(self, instance):
raise AttributeError("Can't delete")
class Typed(Descriptor):
ty = object
def __set__(self, instance, value):
if not isinstance(value, self.ty):
raise TypeError('Expected %s' % self.ty)
super().__set__(instance, value)
class String(Typed):
ty = str
# Length checking
class Sized(Descriptor):
def __init__(self, *args, maxlen, **kwargs):
print("inside sized")
self.maxlen = maxlen
super().__init__(*args, **kwargs)
def __set__(self, instance, value):
if len(value) > self.maxlen:
raise ValueError('Too big')
super().__set__(instance, value)
class SizedString(String, Sized):
pass
# Pattern matching
class Regex(Descriptor):
def __init__(self, *args, pat, **kwargs):
print("inside regex")
self.pat = re.compile(pat)
super().__init__(*args, **kwargs)
def __set__(self, instance, value):
if not self.pat.match(value):
raise ValueError('Invalid string')
super().__set__(instance, value)
class SizedRegexString(SizedString, Regex):
pass
# Structure definition code
def make_signature(names):
return Signature(
Parameter(name, Parameter.POSITIONAL_OR_KEYWORD)
for name in names)
class StructMeta(type):
@classmethod
def __prepare__(cls, name, bases):
return OrderedDict()
def __new__(cls, clsname, bases, clsdict):
fields = [key for key, val in clsdict.items()
if isinstance(val, Descriptor) ]
for name in fields:
clsdict[name].name = name
clsobj = super().__new__(cls, clsname, bases, dict(clsdict))
sig = make_signature(fields)
setattr(clsobj, '__signature__', sig)
return clsobj
class Structure(metaclass=StructMeta):
def __init__(self, *args, **kwargs):
bound = self.__signature__.bind(*args, **kwargs)
for name, val in bound.arguments.items():
setattr(self, name, val)
if __name__ == '__main__':
class Stock(Structure):
name = SizedRegexString(maxlen=8, pat='[A-Z]+$')
for item in SizedRegexString.__mro__:
print(item)
init 中打印语句的输出:
inside sized
inside regex
inside desc
inside desc
inside desc
SizedRegexString 的 mro 输出 class
<class '__main__.SizedRegexString'>
<class '__main__.SizedString'>
<class '__main__.String'>
<class '__main__.Typed'>
<class '__main__.Sized'>
<class '__main__.Regex'>
<class '__main__.Descriptor'>
<class 'object'>
init和set两个调用链是否都遵循mro?或者这里发生了其他事情?
我不清楚你的问题到底是什么,所以如果你能准确地解释你期望发生的事情,以及这与实际发生的事情有何不同,那将会很有帮助。鉴于这一事实,我将尝试在此处解释如何评估 MRO。
首先,由于示例代码中的class层次结构比较复杂,可能有助于可视化继承结构:
关于你的问题,
Does init and set both call chains follow the mro?
如果我没理解错的话,简短的回答是肯定的。 MRO是根据class继承决定的,是classes的属性,不是方法。你通过 SizedRegexString.__mro__
的循环说明了这个事实,所以我猜你的问题是由于 __init__
和 __set__
的调用链之间的感知差异引起的。
__init__ 调用链
SizedRegexString.__init__
的调用链如下:
SizedRegexString.__init__
,没有明确定义,所以遵从其超class的定义SizedString.__init__
,未明确定义String.__init__
,未明确定义Typed.__init__
,未明确定义Sized.__init__
,设置maxlen
,然后调用super().__init__()
Regex.__init__
,设置pat
,然后调用super().__init__()
Descriptor.__init__
,设置name
所以在调用 SizedRegexString.__init__
时,根据 MRO,有七个定义的 classes 需要检查 __init__
方法(假设每个调用 super().__init__()
,还有)。但是,正如您所指出的,__init__
方法中的 print 语句的输出显示访问了以下 classes:Sized
、Regex
和 Descriptor
.请注意,这些 classes - 顺序相同 - 与上面项目符号中明确定义的那些相同。
因此,对我们来说,似乎 SizedRegexString
的 MRO 是 [Sized
、Regex
、Descriptor
] 因为这是我们看到的仅有的三个 classes 实际在做事。然而,这种情况并非如此。上面带项目符号的 MRO 仍然被遵守,但是 Sized
之前的 classes 的 none 明确定义了一个 __init__
方法,所以他们每个人都默默地遵守他们的 superclass是的。
__set__ 调用链
这解释了 __init__
如何遵循 MRO,但为什么 __set__
似乎表现不同?要回答这个问题,我们可以遵循上面使用的相同项目符号 MRO:
SizedRegexString.__set__
,没有明确定义,所以遵从它的超class的定义SizedString.__set__
,未明确定义String.__set__
,未明确定义Typed.__set__
,检查value
是self.ty
的实例,然后调用super().__set__()
Sized.__set__
,检查value
的长度,然后调用super().__set__()
Regex.__set__
,确保self.pat
和value
之间的匹配,然后调用super().__set__()
Descriptor.__set__
,将self.name
和value
的 key/value 对添加到instance.__dict__
这里的要点是 __set__
遵守与 __init__
相同的 MRO,因为它们属于相同的 class,即使我们从四个不同的地方看到 activity class 这次是,而我们只看到三个 __init__
。因此,再一次,看起来好像SizedRegexString
的MRO现在是[Typed
、Sized
、Regex
、Descriptor
]。这可能会造成混淆,因为这个新的调用链不同于 SizedRegexString.__mro__
和我们在 SizedRegexString.__init__
中看到的明显调用链。
TL;DR
但是在跟踪 __init__
和 __set__
的调用链之后,我们可以看到它们都遵循 MRO。差异来自于 Descriptor
的更多后代显式定义 __set__
方法而不是 __init__
方法。
加分
以下几点可能会引起一些混淆:
None 定义的
__set__
方法实际上在您的示例代码的当前状态下被调用。我们可以从您的示例代码中找出以下两行原因:class Stock(Structure): name = SizedRegexString(maxlen=8, pat=“[A-Z]+$”)
这两条线的最终产品 (
Stock
) 是由StructMeta
metaclass 的__new__
方法生成的。虽然Stock
确实具有name
class 属性,它是一个SizedRegexString
实例,但未设置此实例的任何属性。因此,调用了__set__
个方法中的 none 个。我们期望__set__
被调用的地方是在Stock.__init__
中,因为Structure.__init__
中的以下行:for n, v in bound.arguments.items(): setattr(self, n, v)
通过在示例代码的末尾添加
s = Stock(name=“FOO”)
,我们可以看到__set__
方法执行成功。此外,我们可以验证Regex.__set__
和Sized.__set__
以及s = Stock(name=“foo”)
和s = Stock(name=“FOOFOOFOO”)
分别引发了正确的错误在Python 3.6之后,
dict
默认是有序的,所以StructMeta
中的__prepare__
方法可能是多余的,具体取决于Python 您正在使用的版本
希望我回答了你的问题。如果我完全错过了要点,如果您能准确说明您的期望,我很乐意再试一次。