Метакласс, разница между new и init
Какая разница между методами __new__
и __init__
в метаклассе, если доступен одинаковый набор параметров mcls
, name
, bases
, namespace
Ответы (1 шт):
Процесс конструирования экземпляра класса состоит из двух шагов:
- Создание нового пустого экземпляра класса.
- Инициализация созданного экземпляра класса.
Первый шаг: использует магический метод __new__()
, который отвечает за создание и возврат нового пустого экземпляра класса.
Второй шаг: созданный экземпляр передается в метод __init__()
для инициализации, то есть для добавления ему необходимых атрибутов.
Реализация по умолчанию (print для наглядности):
def __new__(cls, *args, **kwargs):
print('1. Вызов метода __new__()')
print(cls)
return super().__new__(cls)
def __init__(self, test):
print('2. Вызов метода __init__()')
self.test = test
print(self)
__new__()
принимает один обязательный аргумент —
класс, экземпляр которого требуется создать, для этого вызывается __new__()
класса object
отвечающего за создание всех объектов. По этому сперва происходит создание экземпляра — вызов метода __new__()
, а затем его инициализация — вызов метода __init__()
.
Таким образом:
Основная задача метода
__new__()
заключается в создании и возврате нового пустого экземпляра класса, который после будет передан в инициализатор.Основная задача метода
__init__()
заключается в наделении необходимыми атрибутами только что созданного пустого экземпляра класса.
Обычно нет необходимости вмешиваться в процесс создания экземпляра класса (модифицировать __new__()
). Но предположим, мы хотим реализовать класс Distance, наследника класса float, описывающего расстояние на местности, который помимо своего числового значения имеет атрибут unit, в котором содержатся единицы измерения этого расстояния.
Если мы опустим переопределение метода __new__()
и попытаемся решить задачу лишь с помощью метода __init__()
, то получим исключение - TypeError: float expected at most 1 argument, got 2.
class Distance(float):
def __init__(self, value, unit):
super().__init__()
self.unit = unit
distance = Distance(1, 'Meters')
Суть в том, что при наследовании от неизменяемых типов данных их значение определяется при создании, то есть в методе __new__()
, и менять его в инициализаторе уже поздно.
Кроме того, выражение Distance(1, 'Meters')
сперва вызывает метод __new__()
класса float, который принимает лишь один аргумент — числовое или строковое значение, на основе которого будет создано число, а в нашем случае их передается два, что и приводит к ошибке.
Но если расширить метод __new__()
:
class Distance(float):
def __new__(cls, value, unit):
instance = super().__new__(cls, value)
instance.unit = unit
return instance
distance = Distance(1, 'Meters')
print(distance)
print(distance.unit)
Вывод:
1.0
Meters
Пример с metaclass
:
import re
class PrivateAccessMeta(type):
def __new__(cls, name, bases, dct):
print(cls)
print(name)
print(bases)
print(dct)
new_dct = dct.copy()
for key in new_dct.keys():
if key.startswith(f'_{name}') and not key.endswith('__'):
pattern = re.compile(r'_(\w+)__(.*)', re.IGNORECASE)
result = pattern.search(key).group(2)
dct.update({result: property(
lambda self, prop=key: getattr(self, prop),
lambda self, value, prop=key: setattr(self, prop, value)
)})
return super().__new__(cls, name, bases, dct)
class A(metaclass=PrivateAccessMeta):
__n= "Приват A __n"
__z= "Приват A __z"
__y= "Приват A __y"
__h= "Приват A __n"
class B(A, metaclass=PrivateAccessMeta):
__n = "Приват B __n"
def __init__(self, unit):
self.unit = unit
obj_a = A()
obj_b = B(975786)
print(obj_a.n)
print(obj_a.z)
print(obj_b.n)
print(obj_b.z)
print(obj_b.y)
print(obj_b.unit)
print(dir(obj_b))
obj_b.y = 8989
print(obj_b.y)
Вывод:
<class '__main__.PrivateAccessMeta'>
A
()
{'__module__': '__main__', '__qualname__': 'A', '_A__n': 'Приват A __n', '_A__z': 'Приват A __z', '_A__y': 'Приват A __y', '_A__h': 'Приват A __n'}
<class '__main__.PrivateAccessMeta'>
B
(<class '__main__.A'>,)
{'__module__': '__main__', '__qualname__': 'B', '_B__n': 'Приват B __n', '__init__': <function B.__init__ at 0x70bb4b8fe0>}
Приват A __n
Приват A __z
Приват B __n
Приват A __z
Приват A __y
975786
['_A__h', '_A__n', '_A__y', '_A__z', '_B__n', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'h', 'n', 'unit', 'y', 'z']
8989