Подтвердить что ты не робот

Понимание __init_subclass__

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

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

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

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'

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

4b9b3361

Ответ 1

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

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

Как отмечает PEP:

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

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

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

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

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

Ответ 2

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__(cls, **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

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

Ответ 3

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

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

Основное предназначение на удобочитаемость/ремонтопригодность перспектива более четкого выделения "подкласса подклассов" Инициализация "случай из" настраивает поведение во время выполнения подклассы ".

Полностью настраиваемый метакласс не дает никаких указаний на объем, а __init_subclass__ более четко указывает, что нет постоянное воздействие на создание пост-подкласса поведения.

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


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

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

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

Ответ 4

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

Фон

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

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