<jupyter_start><jupyter_text>Метаклассы и создание экземпляра> “Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don’t (the people who actually need them know with certainty that they need them, and don’t need an explanation about why).”                                                                                                   — <NAME> Создание нового экземпляраВы уже знаете, что конструктор в питоне объявляется магическим методом `__init__(self, ...) -> None`. Но в момент вызова этого метода объект уже создан:<jupyter_code>class A:
    def __init__(self):
        print("Только вошли в конструктор, а тут уже есть self =", self)
        
A()<jupyter_output><empty_output><jupyter_text>Но что если мы хотим изменить объект до его создания? Классыhttps://habr.com/ru/post/114585/https://refactoring.guru/ru/design-patterns/abstract-factoryКлассы в питоне - это объектные фабрики.__Абстрактная фабрика__ — это порождающий паттерн проектирования, который позволяет создавать семейства связанных объектов, не привязываясь к конкретным классам создаваемых объектов.__Фабрика__ - объект, создающий другие объекты, которые считаются новыми.![interface.png](attachment:interface.png)![factory.png](attachment:factory.png)Инстанцирование объекта происходит в 2 этапа:1. Вызывается классовый метод `__new__(cls, *args, **kwargs) -> object`, который, хоть и не объявляется `@classmethod`, но является таким;2. У объекта, возвращенного в предыдущем пункте, вызывается метод `__init__(self, *args, **kwargs) -> None`, в котором описываются изначальные действия с объектом.<jupyter_code>class A(object):
    def __new__(cls, value):
        obj = super(A, cls).__new__(cls)
        print(f"Создался объект {obj} класса {cls} с аргументом {value}")
        return obj
    
    def __init__(self, value):
        self.val = value
        print(f"Инициализируем объект {self} с аргументом {value}")


a = A(25)<jupyter_output><empty_output><jupyter_text>Реализация паттерна синглтон (одиночка)https://refactoring.guru/ru/design-patterns/singleton__Синглтон__ - это порождающий паттерн проектирования, который гарантирует, что у класса есть только один экземпляр, и предоставляет к нему глобальную точку доступа. Т.е. при вызове конструктора такого класса должен возвращаться уже созданный однажды объект. (Новый должен вернуться только при самом первом вызове конструктора синглтона). Пример применения таких классов - класс некоторого приложения, которое не должно быть открыто дважды.![singleton.png](attachment:singleton.png)<jupyter_code>class Singleton(object):
    instance = None
    
    def __new__(cls):
        if cls.instance is None:
            cls.instance = super(Singleton, cls).__new__(cls)
        return cls.instance
    
    
Singleton() is Singleton()
a = Singleton()
a.x = 1
b = Singleton()
print("b.x =", b.x)
b.y = 2
print("a.y =", a.y)<jupyter_output><empty_output><jupyter_text>МетаклассыВ питоне всё является объектами, в том числе и сами классы. А какой тип у класса?<jupyter_code>Singleton.__class__<jupyter_output><empty_output><jupyter_text>Тот же самый, что у базовых типов, функций, базового класса object и, соответственно, класса type:<jupyter_code>object.__class__
type.__class__<jupyter_output><empty_output><jupyter_text>object - это базовый класс, от которого наследуются абсолютно все объекты в Python 3+. type - это класс, который символизирует тип. Вся магия со словарем `__dict__` в объектах происходит именно благодаря классу type. В объектах класса object нет атрибута `__dict__`.<jupyter_code>type(3).__dict__
object().__dict__<jupyter_output><empty_output><jupyter_text>Рассмотрим отношения классов object и type. Базовым классом любого объекта является object:<jupyter_code>str.__mro__
Singleton.__mro__
type.__mro__
object.__mro__<jupyter_output><empty_output><jupyter_text>А вот isinstance выдает странные вещи для этих двух базовых типов:<jupyter_code>isinstance(object, type), isinstance(object, object)
isinstance(type, type), isinstance(type, object)
isinstance(int, object), isinstance(int, type)
isinstance(Singleton, object), isinstance(Singleton, type)<jupyter_output><empty_output><jupyter_text>Копнем чуть глубже и посмотрим на то, что из этого подкласс другого:<jupyter_code>issubclass(type, object)
issubclass(object, type)
issubclass(Singleton, object), issubclass(Singleton, type)
issubclass(str, object), issubclass(str, type)<jupyter_output><empty_output><jupyter_text>Определение метаклассаПолучается, что классы - это расширения базового класса __object__. А __type__ - это __метакласс__.__Метакласс__ - это класс, экземплярами которого являются другие классы. Всё, что определено в метаклассе, доступно для классов, но недоступно для объектов. Отношение класс-метакласс получается таким же, как объект-класс.![metaclass.png](attachment:metaclass.png)![type_and_object.png](attachment:type_and_object.png)Поиск атрибутов класса происходит так же, как в объектах:1. Поиск атрибута в `__dict__` класса;2. Если не найден, то в `__dict__` метакласса.<jupyter_code># метаклассы наследуются от type
class Ameta(type):
    def foo(cls):
        print("Ameta.foo")
    

class A(object, metaclass=Ameta):
    pass


A.foo()
A().foo()<jupyter_output><empty_output><jupyter_text>Так же, как и в случае с привязкой методов к объекту класса, метод метакласса, вызванный из класса, является bound методом, а вызванный из метакласса - обычной функцией.<jupyter_code>A.foo, Ameta.foo<jupyter_output><empty_output><jupyter_text>Но если определяем в классе свой атрибут foo, обращение из объекта будет вызывать его:<jupyter_code>class B(A):
    @classmethod
    def foo(cls):
        print("B.foo")
        
        
B.foo()
B().foo()<jupyter_output><empty_output><jupyter_text>Динамическое создание классовhttps://proglib.io/p/metaclasses-in-python/Есть 2 способа создать класс. Один мы уже знаем - это объявить его прямо в коде в блоке `class`. Другой способ - использовать встроенную функцию `type`.<jupyter_code>MyShinyClass = type('MyShinyClass', (), {})

print(MyShinyClass)
print(MyShinyClass())<jupyter_output><empty_output><jupyter_text>Общий синтаксис функции type:`type(, (), {})`<jupyter_code>ShinyChild = type(
    'ShinyChild',
    (MyShinyClass,),
    {
        'true_value': True,
        'upper': lambda self, sth: sth.upper()
    }
)

print(ShinyChild().true_value)
print(ShinyChild().upper('abcd'))<jupyter_output><empty_output><jupyter_text>Алгоритм создания классаПовторим, как создается объект:![instance-creation.png](attachment:instance-creation.png)Похожим образом создается класс:![class-creation.png](attachment:class-creation.png)<jupyter_code>class AMeta(type):
    def __new__(mcs, name, bases, classdict, **kwargs):
        cls = super().__new__(mcs, name, bases, classdict)
        print('Meta __new__', cls)
        return cls

    def __init__(cls, name, bases, classdict, **kwargs):
        print('Meta __init__', cls)
        super().__init__(name, bases, classdict, **kwargs)

    def __call__(cls):
        print('Meta __call__')
        return super().__call__()

    @classmethod
    def __prepare__(mcs, name, bases, **kwargs):
        print('Meta __prepare__', **kwargs)
        return {'b': 2, 'a': 2}


class A(metaclass=AMeta):
    a = 1

    def __new__(cls):
        self = super().__new__(cls)
        print('class __new__', self)
        return self

    def __init__(self):
        print('class __init__')

    def __call__(self):
        print('class __call__')
        pass


a = A()
print()
print("Объект создан, теперь обратимся к атрибутам:")
print(a.a)
print(a.b)

print()
print("А теперь создадим еще один класс (не объект):")
Another = AMeta('Test', (), {}, kw1=1, kw2=2)<jupyter_output><empty_output><jupyter_text>Пользовательские метаклассыНапишем метакласс, который позволяет использовать нотацию нижнего_подчеркивания для доступа к атрибутам, заданным camelCase'ом.<jupyter_code>import re


class CamelToUnderscoreMeta(type):
    def __new__(mcs, name, mro, attrs):
        """
          Возвращает класс с переопределенными названиями атрибутов.
        """
        def camel2undrscr(s):
            """Преобразует все заглавные буквы в строке в _ и маленькую букву"""
            return re.sub('([A-Z])', lambda pattern: '_' + pattern.group(1).lower(), s)

        # преобразуем все атрибуты, кроме магических
        new_attrs = {}
        for name, val in attrs.items():
            if not name.startswith('__'):
                new_attrs[camel2undrscr(name)] = val
            else:
                new_attrs[name] = val

        # возвращаем класс, прокидывая имя и родителей
        return super(CamelToUnderscoreMeta, mcs).__new__(mcs, name, mro, new_attrs)
class Foo(metaclass=CamelToUnderscoreMeta):
    someCodeFromCLib = 12345
    
    
print(Foo().some_code_from_c_lib)
print(Foo().someCodeFromCLib)<jupyter_output><empty_output><jupyter_text>Метакласс-синглтонhttps://habr.com/ru/post/114587/Определять в метаклассе можно не только метод `__new__`, но и любые другие.<jupyter_code>class SingletonMeta(type):
    def __init__(cls, *args, **kw):
        cls.instance = None
        
    def __call__(cls, *args, **kw):
        if cls.instance is None:
            cls.instance = super(SingletonMeta, cls).__call__(*args, **kw)
        return cls.instance
    

class SingletonFromMeta(metaclass=SingletonMeta):
    def __init__(self, default):
        self.default = default


a = SingletonFromMeta(5)
a.x = 1
print(SingletonFromMeta(88).default)
print(SingletonFromMeta().x)<jupyter_output><empty_output><jupyter_text>Пример из популярных библиотекВ Django есть собственная ORM - обертка над базой данных. Классы там переопределены так, чтобы при определенных условиях брать как названия, так и значения атрибутов из таблицы в базе данных, к которой подключается серверное приложение. Вот пример:<jupyter_code>class Person(models.Model):
    name = models.CharField(max_length=30)
    age = models.IntegerField()
    
    
guy = Person(name='bob', age='35')
print(guy.age)

# возвращается int-объект 35<jupyter_output><empty_output><jupyter_text>Абстрактные классы в Python. Модуль abcНесмотря на то, что абстрактные классы можно задать вручную, прописав в методах вызов ошибки NotImplementedError, этот метод имеет недостатки. Главный из них - методы, которые забыли переопределить, дадут ошибку только в рантайме при непосредственной попытке обратиться к ним. Метакласс, определенный в библиотеке abc, вызывает ошибку определения класса еще на стадии создания экземпляра.<jupyter_code>import abc


class C(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def my_abstract_method(self):
        pass

    @classmethod
    @abc.abstractmethod
    def my_abstract_classmethod(cls):
        pass


class SomeWrongImplementationC(C):
    def my_abstract_method(self):
        pass


class SomeCorrectImplementationC(C):
    def my_abstract_method(self):
        pass

    @classmethod
    def my_abstract_classmethod(cls):
        pass


print(SomeCorrectImplementationC())
print(SomeWrongImplementationC())<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10024274026791333, 'fraction_numerical': 0.004585093949474063, 'mean_word_length': 4.490621915103652, 'pattern_counts': {'":': 0, '<': 78, '<?xml version=': 0, '>': 82, 'https://': 5, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '19', 'dir': 'jupyter-structured-clean-dedup', 'id': '344241', 'license': 'CC0-1.0', 'n_tokens_mistral': 4102, 'n_tokens_neox': 4503, 'n_words': 938, 'path': 'lecture_4/04-5. Metaclasses.ipynb', 'repo_name': 'LanaLarsen/python_developer', 'size': '11127'}