python中复杂多重继承中的构造函数调用序列

2024-10-03 15:31:55 发布

您现在位置: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内的print语句输出:

inside sized
inside regex
inside desc
inside desc
inside desc

SizedRegexString类的mro输出

<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?或者这里发生了什么事?你知道吗


Tags: instancenameselfinitvaluemaindefclass
1条回答
网友
1楼 · 发布于 2024-10-03 15:31:55

我不清楚你的问题到底是什么,所以如果你能准确地解释一下你预期会发生什么,以及这与实际发生的情况有何不同,那将是很有帮助的。有鉴于此,我将尝试解释如何评估MRO。你知道吗

首先,由于示例代码中的类层次结构相当复杂,因此可以帮助可视化继承结构:

enter image description here

关于你的问题

Does init and set both call chains follow the mro?

如果我理解正确的话,简短的回答是肯定的。MRO是基于类继承确定的,是的属性,而不是方法。你通过SizedRegexString.__mro__的循环说明了这个事实,所以我猜你的问题是由__init____set__的调用链之间的感知差异引起的。你知道吗

__初始化调用链

SizedRegexString.__init__的调用链如下:

  • SizedRegexString.__init__,它没有显式定义,因此它遵从其超类的定义
  • SizedString.__init__,未明确定义
  • String.__init__,未明确定义
  • Typed.__init__,未明确定义
  • Sized.__init__,设置maxlen,然后调用super().__init__()
  • Regex.__init__,设置pat,然后调用super().__init__()
  • Descriptor.__init__,设置name

因此,在调用SizedRegexString.__init__时,根据MRO,有七个定义的类需要检查__init__方法(假设每个类也调用super().__init__())。但是,正如您所注意到的,__init__方法内的print语句的输出显示访问了以下类:SizedRegexDescriptor。请注意,这些类与上面的项目符号中明确定义的类是相同的,顺序相同。你知道吗

所以,对我们来说,看起来像是SizedRegexString的MRO是[SizedRegexDescriptor],因为这是我们看到的唯一三个真正在做事情的类。然而,事实并非如此。上面带项目符号的MRO仍然被遵守,但是Sized之前的类都没有显式地定义一个__init__方法,因此它们都默默地遵从它们的超类。你知道吗

__设置呼叫链

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

  • SizedRegexString.__set__,它没有显式定义,因此它遵从其超类的定义
  • SizedString.__set__,未明确定义
  • String.__set__,未明确定义
  • Typed.__set__,检查value是否是self.ty的实例,然后调用super().__set__()
  • Sized.__set__,它检查value的长度,然后调用super().__set__()
  • Regex.__set__,确保self.patvalue之间匹配,然后调用super().__set__()
  • Descriptor.__set__,它将self.namevalue的键/值对添加到instance.__dict__

这里的结论是__set____init__遵循相同的MRO,因为它们属于同一类,尽管我们这次看到了来自四个不同类的活动,而我们只看到了三个具有__init__的活动。因此,再一次,它看起来像是SizedRegexString的MRO现在是[TypedSizedRegexDescriptor]。这可能令人困惑,因为这个新的调用链既不同于SizedRegexString.__mro__,也不同于我们看到的SizedRegexString.__init__的明显调用链。你知道吗

TL;博士

但是在遵循__init____set__的调用链之后,我们可以看到它们都遵循MRO。这种差异来自于这样一个事实:Descriptor的更多后代显式地定义了__set__方法,而不是__init__方法。你知道吗

其他要点

以下是可能会引起一些混乱的其他几点:

  1. 在示例代码的当前状态中,没有一个定义的__set__方法被实际调用。我们可以通过示例代码中的以下两行来找出原因:你知道吗

    class Stock(Structure):
        name = SizedRegexString(maxlen=8, pat=“[A-Z]+$”)
    

    这两行(Stock)的最终产物是由StructMeta元类的__new__方法生成的。虽然Stockname类属性是SizedRegexString实例,但没有设置此实例的属性。因此,没有调用任何__set__方法。我们希望调用__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. 在python3.6之后,dict在默认情况下是有序的,因此StructMeta中的__prepare__方法可能是多余的,这取决于您使用的Python版本

希望我回答了你的问题。如果我完全没有抓住重点,我很乐意再试一次,如果你能澄清你到底期待什么。你知道吗

相关问题 更多 >