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'>

initset两个调用链是否都遵循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:SizedRegexDescriptor .请注意,这些 classes - 顺序相同 - 与上面项目符号中明确定义的那些相同。

因此,对我们来说,似乎 SizedRegexString 的 MRO 是 [SizedRegexDescriptor] 因为这是我们看到的仅有的三个 classes 实际在做事。然而,这种情况并非如此。上面带项目符号的 MRO 仍然被遵守,但是 Sized 之前的 classes 的 none 明确定义了一个 __init__ 方法,所以他们每个人都默默地遵守他们的 superclass是的。

__set__ 调用链

这解释了 __init__ 如何遵循 MRO,但为什么 __set__ 似乎表现不同?要回答这个问题,我们可以遵循上面使用的相同项目符号 MRO:

  • SizedRegexString.__set__,没有明确定义,所以遵从它的超class的定义
  • SizedString.__set__,未明确定义
  • String.__set__,未明确定义
  • Typed.__set__,检查 valueself.ty 的实例,然后调用 super().__set__()
  • Sized.__set__,检查 value 的长度,然后调用 super().__set__()
  • Regex.__set__,确保 self.patvalue 之间的匹配,然后调用 super().__set__()
  • Descriptor.__set__,将 self.namevalue 的 key/value 对添加到 instance.__dict__

这里的要点是 __set__ 遵守与 __init__ 相同的 MRO,因为它们属于相同的 class,即使我们从四个不同的地方看到 activity class 这次是,而我们只看到三个 __init__。因此,再一次,看起来好像SizedRegexString的MRO现在是[TypedSizedRegexDescriptor]。这可能会造成混淆,因为这个新的调用链不同于 SizedRegexString.__mro__ 和我们在 SizedRegexString.__init__ 中看到的明显调用链。

TL;DR

但是在跟踪 __init____set__ 的调用链之后,我们可以看到它们都遵循 MRO。差异来自于 Descriptor 的更多后代显式定义 __set__ 方法而不是 __init__ 方法。

加分

以下几点可能会引起一些混淆:

  1. 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”) 分别引发了正确的错误

  2. 在Python 3.6之后,dict默认是有序的,所以StructMeta中的__prepare__方法可能是多余的,具体取决于Python 您正在使用的版本

希望我回答了你的问题。如果我完全错过了要点,如果您能准确说明您的期望,我很乐意再试一次。