Последовательность вызовов конструктора в сложном множественном наследовании в Python - PullRequest
0 голосов
/ 16 декабря 2018

У меня есть сомнения в понимании заявления

name = SizedRegexString (maxlen = 8, pat = '[AZ] + $')

в коде ниже,Я не могу понять, как вызовы инициализации происходят в иерархии.

# 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

Вывод из mro класса SizedRegexString

<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?Или здесь что-то еще происходит?

1 Ответ

0 голосов
/ 28 декабря 2018

Мне неясно, что именно представляет собой ваш вопрос, поэтому было бы полезно, если бы вы могли точно объяснить, что вы ожидали, и как это отличалось от того, что произошло на самом деле.В свете этого я попытаюсь объяснить, как MRO оценивается здесь.

Во-первых, поскольку иерархия классов в примере кода довольно запутана, это может помочь визуализировать структуру наследования:

enter image description here

Обращаясь к вашему вопросу,

Соблюдает ли init и set обе цепочки вызовов mro?

Если я правильно понимаю, короткий ответ - да.MRO определяется на основе наследования классов и является атрибутом классов , а не методов.Ваш цикл по SizedRegexString.__mro__ иллюстрирует этот факт, поэтому я предполагаю, что ваш вопрос возник из-за несоответствия между цепочками вызовов __init__ и __set__.

__ init__ Цепочка вызовов

Цепочка вызовов для 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__(), какЧто ж).Однако, как вы заметили, выходные данные операторов print внутри методов __init__ показывают, что посещаются следующие классы: Sized, Regex и Descriptor.Обратите внимание, что это те же классы - в том же порядке, что и те, которые упомянуты в пулях выше как явно определенные.

Итак, для нас это может показаться похожим на MRO для SizedRegexString [Sized, Regex, Descriptor], потому что это только три класса, которые мы видимна самом деле делать вещи.Однако, это не так.Приведенная выше маркированная MRO все еще соблюдается, но ни один из классов до Sized явно не определяет метод __init__, поэтому каждый из них молча откладывает свои суперклассы.

__ set__ Call Chain

Это объясняет, как __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.pat и value, затем вызывает super().__set__()
  • Descriptor.__set__, который добавляет пару ключ / значение self.name и value к instance.__dict__

Вывод здесь заключается в том, что __set__ придерживаетсято же MRO, что и __init__, потому что они принадлежат к одному и тому же классу, хотя в этот раз мы видим активность четырех разных классов, тогда как мы видели только три с __init__.Итак, еще раз, может показаться , как будто MRO SizedRegexString теперь [Typed, Sized, Regex, Descriptor].Это может сбивать с толку, поскольку эта новая цепочка вызовов отличается как от SizedRegexString.__mro__, так и от кажущейся цепочки вызовов, которую мы видели для SizedRegexString.__init__.

TL; DR

Но после следования цепочкам вызовов для __init__ и __set__, мы можем видеть, что они оба следуют MRO.Несоответствие происходит из-за того, что больше потомков Descriptor явно определяют метод __set__, чем метод __init__.

Дополнительные точки

Вот еще пара моментов, которые могут бытьвызывая некоторую путаницу:

  1. Ни один из определенных __set__ методов фактически не вызывается в текущем состоянии кода вашего примера.Мы можем выяснить, почему из следующих двух строк из вашего примера кода:

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

    Конечный продукт этих двух строк (Stock) создается методом __new__ метакласса StructMeta.Хотя Stock имеет атрибут класса name, который является экземпляром 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. После Python 3.6, dict sупорядочено по умолчанию, поэтому метод __prepare__ в StructMeta может оказаться излишним, в зависимости от того, какую версию Python вы используете

Надеюсь, я рассмотрел ваш вопрос.Если бы я полностью упустил момент, я был бы рад попробовать еще раз, если бы вы могли уточнить, что именно вы ожидаете.

...