Понимание __init_subclass__

Я наконец обновил свою версию Python и обнаружил добавленные новые функции. Среди прочего, я ломал голову над новым __init_subclass__ метод. Из документов:

Этот метод вызывается всякий раз, когда содержащий класс является подклассом. cls - это новый подкласс. Если он определен как обычный метод экземпляра, этот метод неявно преобразуется в метод класса.

Итак, я начал немного поиграться с этим, следуя примеру в документации:

class Philosopher:
    def __init_subclass__(cls, default_name, **kwargs):
        super().__init_subclass__(**kwargs)
        print(f"Called __init_subclass({cls}, {default_name})")
        cls.default_name = default_name

class AustralianPhilosopher(Philosopher, default_name="Bruce"):
    pass

class GermanPhilosopher(Philosopher, default_name="Nietzsche"):
    default_name = "Hegel"
    print("Set name to Hegel")

Bruce = AustralianPhilosopher()
Mistery = GermanPhilosopher()
print(Bruce.default_name)
print(Mistery.default_name)

Производит этот вывод:

Called __init_subclass(<class '__main__.AustralianPhilosopher'>, 'Bruce')
'Set name to Hegel'
Called __init_subclass(<class '__main__.GermanPhilosopher'>, 'Nietzsche')
'Bruce'
'Nietzsche'

Я понимаю, что этот метод вызывается после определения подкласса, но мои вопросы касаются, в частности, использования этой функции. Я прочитал статью PEP 487 тоже, но мне это не сильно помогло. В чем может быть полезен этот метод? Это для:

  • суперкласс для регистрации подклассов при создании?
  • принуждение подкласса установить поле во время определения?

Кроме того, нужно ли мне понимать __set_name__, чтобы полностью понять его использование?


person EsotericVoid    schedule 30.07.2017    source источник


Ответы (4)


__init_subclass__ и __set_name__ - ортогональные механизмы - они не связаны друг с другом, а просто описаны в одном и том же PEP. Обе функции раньше требовали полнофункционального метакласса. В PEP 487 рассматриваются 2 наиболее распространенных использования метаклассов:

  • как сообщить родителю, когда он подклассифицируется (__init_subclass__)
  • как сообщить классу дескриптора имя свойства, для которого он используется (__set_name__)

Как говорит политический деятель:

Хотя существует множество возможных способов использования метакласса, подавляющее большинство случаев использования попадает всего в три категории: некоторый код инициализации, выполняемый после создания класса, инициализация дескрипторов и сохранение порядка, в котором были определены атрибуты класса. < / сильный>

Первые две категории могут быть легко получены с помощью простых крючков при создании класса:

  • Хук __init_subclass__, который инициализирует все подклассы данного класса.
  • при создании класса ловушка __set_name__ вызывается для всех атрибутов (дескрипторов), определенных в классе, и

Третья категория - это тема другого PEP, PEP 520.

Также обратите внимание, что, хотя __init_subclass__ является заменой использования метакласса в дереве наследования этого класса, __set_name__ в классе дескриптора является заменой использования метакласса для класса, имеющего экземпляр дескриптора как атрибута.

person Antti Haapala    schedule 30.07.2017
comment
Я не особо разбирался с метаклассами, чтобы полностью понять его использование, но благодаря вашему объяснению (и другим) похоже, что существует множество ситуаций, когда нам нужно завершить определение подкласса после его объявления. - person EsotericVoid; 30.07.2017
comment
Моя система декларативных схем, marrow.schema демонстрирует такой метакласс. Он явно собирает вклады суперкласса (L57), сохраняет исходный порядок определения для переопределенных атрибутов (L64 / 77) и сообщает атрибутам их присвоенные имена (L74), уведомляет атрибуты об их присоединении (L81), а затем уведомляет подкласс о своем построении (L99). Большая часть этого устарела __set_name__ и __init_subclass__, но не все. - person amcgregor; 22.10.2019
comment
Хотя технически правильный, этот ответ мало что объясняет, но при этом многое запутывает. Вместо этого всем просто нужен Мартин Питерс гораздо более читабельный пример из реальной жизни. - person Cecil Curry; 18.04.2020

PEP 487 стремится взять два распространенных варианта использования метаклассов и сделать их более доступными, не разбираясь во всех тонкостях метаклассов. Две новые функции, __init_subclass__ и __set_name__, в остальном независимы, они не зависят друг от друга.

__init_subclass__ - это просто метод перехвата. Вы можете использовать его для чего угодно. Это полезно как для регистрации подклассов, так и для и для установки значений атрибутов по умолчанию для этих подклассов.

Недавно мы использовали это, чтобы предоставить «адаптеры» для различных систем контроля версий, например:

class RepositoryType(Enum):
    HG = auto()
    GIT = auto()
    SVN = auto()
    PERFORCE = auto()

class Repository():
    _registry = {t: {} for t in RepositoryType}

    def __init_subclass__(cls, scm_type=None, name=None, **kwargs):
        super().__init_subclass__(**kwargs)
        if scm_type is not None:
            cls._registry[scm_type][name] = cls

class MainHgRepository(Repository, scm_type=RepositoryType.HG, name='main'):
    pass

class GenericGitRepository(Repository, scm_type=RepositoryType.GIT):
    pass

Это тривиально позволяет нам определять классы обработчиков для конкретных репозиториев, не прибегая к использованию метакласса или декораторов.

person Martijn Pieters    schedule 30.07.2017
comment
Хороший пример. Читая и другие ответы, я понимаю, что это похоже на ярлык для расширения функциональных возможностей базовых классов без необходимости обрабатывать метаклассы. - person EsotericVoid; 30.07.2017
comment
@MartijnPieters в GenericGitRepository, вам не хватает атрибута name в определении класса? отличный пример, мне это нравится. - person Julian Camilleri; 03.03.2019
comment
@JulianCamilleri: нет, дело в том, что параметр name необязательный. - person Martijn Pieters; 03.03.2019
comment
Приносим извинения за все вопросы @MartijnPieters; было любопытно, что происходит на этом этапе: cls._registry[scm_type][name] = cls когда name равно None - есть ли какой-то механизм, который мне не хватает? - person Julian Camilleri; 03.03.2019
comment
Тогда None - это ключ второго уровня. Это такой же ключ, как и имя строки. Таким образом, вы можете зарегистрировать несколько именованных классов и одну «по умолчанию» и безымянную опцию под ключом None. - person Martijn Pieters; 03.03.2019
comment
Я знаю; достаточно честно ... Спасибо. :) - person Julian Camilleri; 03.03.2019
comment
Кому-то еще не пришло в голову, что после того, как я много лет писал на Python, я просто понимаю, что вы можете проиндексировать словарь с помощью None? - person John Allard; 22.10.2020

Основная идея __init_subclass__ заключалась в том, как следует из названия PEP, чтобы предложить более простую форму настройки для классов.

Это ловушка, которая позволяет вам возиться с классами без необходимости знать о метаклассах, отслеживать все аспекты построения классов или беспокоиться о конфликтах метаклассов в будущем. В виде сообщение / a> Ник Коглан на ранней стадии этого PEP заявляет:

Основное предполагаемое преимущество удобочитаемости / ремонтопригодности заключается в более четком различении случая «настраивает инициализацию подкласса» от случая «настраивает поведение подклассов во время выполнения».

Полный настраиваемый метакласс не дает никаких указаний на масштаб воздействия, а __init_subclass__ более четко указывает на отсутствие постоянных эффектов на поведение после создания подкласса.

Метаклассы считаются магическими по какой-то причине: вы не знаете, какими будут их эффекты после того, как класс будет создан. __init_subclass__, с другой стороны, это просто еще один метод класса, он запускается один раз, а затем готово. (точную функциональность см. в документации к нему.)


Вся суть PEP 487 заключается в упрощении (то есть устранении необходимости использования) метаклассов для некоторых общих применений.

__init_subclass__ заботится об инициализации посткласса, в то время как __set_name__ (что имеет смысл только для классов дескрипторов) был добавлен для упрощения инициализации дескрипторов. Кроме того, они не связаны.

Упомянутый третий распространенный случай для метаклассов (сохранение порядка определения), , также был упрощен < / а>. Это было решено без ловушки, с использованием упорядоченного сопоставления для пространства имен (которое в Python 3.6 имеет значение dict, но это деталь реализации :-)

person Dimitris Fasarakis Hilliard    schedule 30.07.2017

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

Фон

__init_subclass__ был введен как альтернатива созданию метаклассов. Вот двухминутный обзор PEP 487 в разговор одного из основных разработчиков, Бретта Кэннона.

Рекомендуемые ссылки

person pylang    schedule 31.07.2017