Дело в том, что то, что управляет вызовом __new__
и __init__
обычного класса, это метод __call__
в его метаклассе.Код в методе __call__
для type
, метатипа по умолчанию, написан на C, но его эквивалент в Python будет следующим:
class type:
...
def __call__(cls, *args, **kw):
instance = cls.__new__(cls, *args, **kw) # __new__ is actually a static method - cls has to be passed explicitly
if isinstance(instance, cls):
instance.__init__(*args, **kw)
return instance
Это происходит для большинства экземпляров объектов в Python,в том числе при создании экземпляров самих классов - метакласс неявно вызывается как часть оператора класса.В этом случае __new__
и __init__
, вызываемые из type.__call__
, являются методами самого метакласса .И в этом случае type
действует как «метаметакласс» - понятие редко требуется, но именно оно создает исследуемое вами поведение.
При создании классов type.__new__
будет отвечать за вызовкласс (не метакласс) __init_subclass__
и методы __set_name__
его дескрипторов - так что метод "metametaclass" __call__
не может это контролировать.
Итак, если вы хотите, чтобы аргументы были переданыдля метакласса __init__
, который будет программно изменен, «нормальным» способом будет иметь «метаметакласс», наследующий от type
и отличающийся от самого метакласса, и переопределяющий его метод __call__
:
class MM(type):
def __call__(metacls, name, bases, namespace, **kw):
name = modify(name)
cls = metacls.__new__(metacls, name, bases, namespace, **kw)
metacls.__init__(cls, name, bases, namespace, **kw)
return cls
# or you could delegate to type.__call__, replacing the above with just
# return super().__call__(modify(name), bases, namespace, **kw)
Конечно, это способ приблизиться к «черепахам до самого дна», чем кто-либо когда-либо хотел бы в производственном коде.
Альтернативой является сохранение измененного имени в качестве атрибута.в метаклассе, чтобы его метод __init__
мог взять оттуда необходимую информацию и игнорировать имя, переданное из его собственного вызова метакласса '__call__
.Информационный канал может быть обычным атрибутом в экземпляре метакласса.Что ж, бывает, что «экземпляр метакласса» - это сам создаваемый класс - и, о, видите, - имя, переданное type.__new__
, уже записано в нем - в атрибуте __name__
.
InДругими словами, все, что вам нужно сделать, чтобы использовать имя класса, измененное в методе метакласса __new__
в его собственном методе __init__
, - это игнорировать переданный аргумент name
и использовать вместо него cls.__name__
:
class Meta(type):
def __new__(mcls, name, bases, namespace, **kw):
name = modified(name)
return super().__new__(mcls, name, bases, namespace, **kw)
def __init__(cls, name, bases, namespace, **kw):
name = cls.__name__ # noQA (otherwise linting tools would warn on the overriden parameter name)
...