Метакласс, разница между new и init

Какая разница между методами __new__ и __init__ в метаклассе, если доступен одинаковый набор параметров mcls, name, bases, namespace


Ответы (1 шт):

Автор решения: Amgarak

Процесс конструирования экземпляра класса состоит из двух шагов:

  1. Создание нового пустого экземпляра класса.
  2. Инициализация созданного экземпляра класса.

Первый шаг: использует магический метод __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__().

Таким образом:

  1. Основная задача метода __new__() заключается в создании и возврате нового пустого экземпляра класса, который после будет передан в инициализатор.

  2. Основная задача метода __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
→ Ссылка