метакласс в python

метакласс в python

касатка

я 🫍 касатка. сегодня изучим метаклассы в python. ну начнем.

‎Если вы не поймете некоторые вещи в этом прологе, то не расстраивайтесь. Я постараюсь объяснить последовательно, от простого к сложному. Однако для полного понимания этого текста надо знать как основы синтаксиса языка Python, так и ООП в Python.

‎Задумывались ли вы, почему функция isinstance(int, object) возвращает True? Объяснение, что все является объектом, которое можно часто услышать, хоть и является правдой, но не дает ответа на вопрос и на самом деле есть профанация, потому что не дает настоящего понимания, а создает только его видимость. Ведь совсем непонятно, почему int (как и любой другой стандартный класс) является непременно экземпляром базового класса. Да, он является подклассом, но почему именно экземпляром? Ответ будет дан по ходу изложения.


‎Для тех, кто изучал Python и не пытался осознать, фраза: «В Python все есть объект!» не несет никакого смысла. Чтобы понять, что это значит, надо прежде всего знать, что такое объект. Объект - это сущность, которая имеет состояние и поведение. Состояние объекта определяется его полями, а поведение определяется его методами. Функция dir позволяет нам увидеть атрибуты объекта.

‎Экземпляр класса есть объект, потому что у него можно создать атрибуты (поля и методы). Классы тоже имеют атрибуты. Ниже показано, что как у классов есть атрибуты, так и у экземпляров этих классов. Вообще все, как обычно, гораздо сложнее. Вот статья, где об этом говорится подробно.

‎class MyClass:

‎ n = 3

‎MyClass.n

‎3

‎instance = MyClass()

‎instance.m = 5

‎instance.m

‎5

‎def greet():

‎ print('hi')

‎instance.f = greet

‎instance.f()

‎hi

‎Объяснить с

‎Приведу некоторые примеры объектов в Python:

‎Встроенные типы данных:

‎Целые числа: 1, -100

‎Вещественные числа: 3.14, -5.2

‎Строки: "Hello", "World"

‎Списки: [1, 2, 3], ["a", "b", "c"]

‎Кортежи: (1, 2, 3), ("a", "b", "c")

‎Множества: {1, 2, 3}, {"a", "b", "c"}

‎Словари: {"name": "John", "age": 30}

‎Объекты, созданные пользователем:

‎Функции: def my_function(): ...

‎Классы: class MyClass: ...

‎Экземпляры классов: my_object = MyClass()

‎Стандартные библиотечные объекты:

‎Файлы: open("myfile.txt", "r")

‎Модули: import math

‎Что такое метакласс?

‎Итак, в Python все является объектом: числа, строки, булевы значения, списки, словари и т.д. Все это объекты в языке Python. Эти объекты созданы, образованы от соответствующих классов или типов данных: int, str, bool, list, dict и т.д. Понятия класс и тип данных, по сути, являются синонимами в Python. Но классы тоже являются объектами. То есть класс объект для создания объектов. Поэтому есть что-то, что может создавать классы. И это метакласс. Метакласс тоже объект (и, в свою очередь, класс). Его нельзя создать другим метаклассом. Он находится на вершине.

‎Здесь можно провести следующую аналогию: Есть какое-то оборудование: инструменты, машины, станки – в общем средства для производства материальных благ. Но есть то, что их производит – средства для производства средств производства – машиностроение, станкостроение или тяжелая промышленность. Конкретное значение, например, целое число 3 – материальное благо, класс – средство для производства этих материальных благ, а метаклассы –средства для производства средств производства.

‎Метакласс это то, что может создавать классы. Можно сказать, что метакласс создает объект-класс по созданию объектов-экземпляров класса. В Python метаклассом является объект type. Это также одноименная функция для определения типа объекта. Но функция type имеет другое применение: она также может создавать классы динамически. То есть одна и та же функция может использоваться для двух совершенно разных вещей в зависимости от передаваемых аргументов. Семантика у type будет совершенно разной в зависимости от того, сколько аргументов вы ему сообщите 1 или 3. Это сделано по историческим причинам, для сохранения обратной совместимости. type принимает на вход описание класса (имя класса, кортеж родительских классов и словарь с атрибутами) и возвращает динамически новый класс, новый тип данных.

‎type(obj) – определить тип

‎type(name, bases, dict) – создать тип

‎Один аргумент:

‎obj : Объект, тип которого требуется определить.

‎Три аргумента:

‎name : Имя для создаваемого типа (становится атрибутом name);

‎bases : Кортеж с родительскими классами (становится атрибутом bases);

‎dict : Словарь, который будет являться пространством имён для тела класса (становится атрибутом dict).

‎На самом деле все стандартные типы данных порождены метаклассом type.

‎type(3) # Целое число порождено классом целых чисел

‎<class 'int'>

‎type(int) # Класс целых чисел, в свою очередь, порожден метаклассом type

‎<class 'type'>

‎class A: pass # Пользовательские классы также создаются этим метаклассом

‎type(A)

‎<class 'type'>

‎type(type) # И даже метакласс type как бы порождает сам себя

‎<class 'type'>

‎Объяснить с

‎То, что type является метаклассом для самого себя, есть уловка на уровне реализации. Он является встроенным метаклассом в ядре языка и не создается каким-либо другим классом или метаклассом.

‎Метакласс: инструмент динамического создания классов

‎Метакласс — это специальный класс, который позволяет программно создавать новые классы. Это полезно в ситуациях, когда необходимо динамически генерировать классы во время выполнения программы.

‎Вместо декларативного создания классов с помощью оператора class, который создает класс статически. Динамическое создание классов позволяет определять поведение и функциональность классов во время выполнения программы, а не только на этапе компиляции.

‎Как создать простейший класс с помощью метакласса

‎Python создает классы с помощью встроенной функции type. Когда вы определяете новый класс, например (в каждом из этих двух примеров я показываю, что классы создаются и их можно инстанцировать):

‎class MyClass:

‎ pass

‎MyClass()

‎<main.MyClass object at 0x00000139620C3C40>

‎Объяснить с

‎Python выполняет следующее:

‎MyClass = type('MyClass', (), {})

‎MyClass()

‎<main.MyClass object at 0x00000139620C3D60>

‎Объяснить с

‎Здесь type создает новый класс, который называется MyClass. Аргументы в () на самом деле представляют собой кортеж базовых классов, от которых наследуется MyClass. В данном случае, поскольку MyClass не имеет явно указанного родительского класса, он наследуется от object по умолчанию. Третий аргумент {} - это словарь, содержащий атрибуты класса MyClass (как поля, так и методы).

‎Рассмотрим более сложный пример.

‎Создание класса с помощью метакласса более сложный случай. Сравнение с классическим созданием

‎Посмотрим на создание класса с помощью type, у которого будет родитель и атрибуты. Но для начала приведу аналог того, как это делается обычно.

‎Классическое создание класса

‎Классическое создание класса

‎Динамическое создание класса, с помощью метакласса

‎Динамическое создание класса, с помощью метакласса

‎Мы создали класс ChildClass сначала обычным способом, а затем так, как Python делает это под капотом.

‎У класса ChildClass есть родитель ParentClass, также он имеет одно поле класса species и два метода init и greet.

‎В функцию type первым аргументов указываем имя класса ChildClass, далее указываем кортеж базовых классов (классов, от которых наследуется наш класс) это будет ParentClass и object (но object указывается автоматически) и последний аргумент словарь с атрибутами. Первый атрибут spicies = 'Homo sapiens' (прописывается имя атрибута в виде строки и через доветочие указывается его значение). Далее надо оговориться:

‎Когда мы динамически создаем класс, то в него можно добавлять и методы. Методы – это тоже атрибуты. Атрибуты, которые ссылаются на объект функцию. Это возможно сделать двумя способами. Для первого способа надо предварительно создать функцию в программе. Во втором с помощью lambda-функций.

‎Добавим два метода init и greet. Для метода init вместо ссылки на реальную функцию укажем lambda-функцию. Для второго метода отдельно зададим функцию в программе и укажем имя метода и через двоеточие передадим ссылку на функцию.

‎Сравниваем выводы: классы родители одинаковые, доступ к полю класса species есть, можно создать экземпляр класса и передать аргументы в конструктор, через ссылку child можно обратиться к полю species и методу greet и получить значение и вывод.

‎То есть с помощью метакласса type динамически в программе создается новый класс ChildClass, который по структуре в точности повторяет первое (классическое) определение. type это метакласс, который Python внутренне использует для создания всех классов.

‎Некоторая справка.

‎В Python есть две функции isinstance и issubclass.

‎Функция isinstance()

‎Функция issubclass()

‎Функция isinstance() в Python проверяет, является ли объект экземпляром указанного класса или его подкласса (обратите внимание). Она принимает два аргумента:

· ‎Объект, который нужно проверить.

· ‎Класс или кортеж классов, с которыми нужно сравнить объект.

‎Функция isinstance() возвращает True, если объект является экземпляром указанного класса или его подкласса, и False в противном случае.

‎Функция issubclass() в Python проверяет, является ли класс подклассом указанного класса или эквивалентен (обратите внимание) ему. Она принимает два аргумента:

· ‎Класс, который нужно проверить.

· ‎Класс или кортеж классов, с которыми нужно сравнить проверяемый класс.

‎Функция issubclass() возвращает True, если проверяемый класс является подклассом указанного класса или эквивалентен ему, и False в противном случае.

‎type.mro

‎Порядок разрешения методов (Method Resolution Order — MRO)

‎Демонстрация работы mro

‎Демонстрация работы mro

‎Содержит кортеж с родительскими типами, выстроенными в порядке разрешения методов.

‎MRO - это список классов, который определяет порядок, в котором Python ищет атрибуты и методы в иерархии классов. MRO используется несколькими методами и операторами в Python, включая issubclass(), isinstance(), а также при поиске атрибутов и методов объекта.

‎Перечислю несколько утверждений, которые разберем дальше:

‎В Python все классы, явно или неявно, наследуются от базового класса object. Это означает, что все классы в Python, включая стандартные встроенные типы, такие как int, float и str, наследуют методы и атрибуты от класса object.

‎Все встроенные (стандартные) типы создаются с помощью метакласса type (и даже object).

‎Так как метакласс type является объектом и также классом то он тоже должен наследоваться от object.

‎Любое конкретное значение является экземпляром класса object.

‎Для краткости я не буду делать много проверок. Буду брать один стандартный тип и проверять его для других результат должен быть идентичен.

‎Объяснение и доказательство первого пункта:

‎issubclass(int, object) # Возвращает True так как любой встроенный класс наследуется от object

‎True

‎class A: pass # Следующая проверка возвращает True так как любой пользовательский класс автоматически будет подклассом object

‎issubclass(A, object)

‎True

‎Объяснить с

‎Вопросы для закрепления:

‎Что выведет issubclass(int, int)?

‎Что выведет issubclass(object, object)?

‎Что выведет issubclass(type, type)?

‎issubclass(int, int)

‎True

‎issubclass(object, object)

‎True

‎issubclass(type, type)

‎True

‎Объяснить с

‎Результаты должны быть понятны так как я просил обратить внимание на то, что функция issubclass() проверяет, является ли класс подклассом указанного класса или эквивалентен ему. Здесь аналогия с множествами: любое множество является подмножеством самого себя.

‎Объяснение и доказательство второго пункта:

‎isinstance(int, type) # Это корректный для нас результат так как в начале я показывал это

‎True

‎isinstance(object, type) # И даже класс стоящий на вершине иерархии object будет экземпляром метакласса type. Возвращает True так как object есть класс, а значит порожден метаклассом.

‎True

‎Объяснить с

‎Еще немного вопросов, ответ на второй вопрос будет дан после доказательства утверждения номер три:

‎Что выведет isinstance(3, int)?

‎Что выведет isinstance(int, object)?

‎Что выведет isinstance(bool, int)?

‎isinstance(3, int) # Этот результат довольно очевиден. Целые числа создаются с помощью класса int

‎True

‎isinstance(int, object) # Вывод этой проверки не понятен

‎True

‎isinstance(bool, int) # bool является подклассом, а не экземпляром int

‎False

‎Объяснить с

‎Вторая проверка вводит в ступор. Неужели базовый object является метаклассом, который порожадет все типы данных? Но в самом начале я показывал, что встроенные типы порождаются type. Противоречие?

‎Подумайте над следующими вопросами перед объяснением третьего утверждения, чтобы лучше прочувствовать их:

‎Что выведет isinstance(type, object)?

‎Что выведет isinstance(type, type)?

‎Что выведет isinstance(object, object)?

‎Объяснение и доказательство третьего пункта (самого интересного потому что даст ответ на вопрос из пролога):

‎issubclass(type, object) # Возвращает True так как type также является классом и должен, как и все классы, наследоваться от object.

‎True

‎isinstance(type, object) # А isinstance проверяет все подклассы, а раз у object подкласс type, то будет выполнена следующая проверка.

‎True

‎isinstance(type, type) # type как бы порождает сам себя (я уже об этом говорил). Это сделано специально, чтобы не существовала бесконечно метаклассов друг над другом.

‎True

‎isinstance(object, object) # Также как и во второй проверке isinstance проверяет все подклассы, а раз у object подкласс type, то будет сделана последняя проверка из пункта два. И проверка возвращает True так как object является экземпляром от type.

‎True

‎Объяснить с

‎То есть object есть экземпляр типа type (предыдущий пункт). Более того, обратное утверждение тоже верно. Но только если мы делаем проверку функцией isinstance(). На самом деле это, конечно, не правда, потому что object - обычный класс и не является метаклассом, тем более порождающим метакласс type.

‎Теперь, наконец, ответ на вопрос из пролога, который вы теперь можете дать сами.

‎Но перед этим вопрос на понимание:

‎Что выведет issubclass(object, type)?

‎issubclass(object, type) # Возвращает False потому что object является суперклассом для всех остальных классов.

‎False

‎Объяснить с

‎Почему же isinstance(int, object) возвращает True?

‎Может возникнуть заблуждение, что если A является экземпляром B (то есть isinstance(A, B) равно True), то можно создать экземпляры A с помощью B. Однако это не всегда верно.

‎То есть неправда то, что если int является экземпляром класса object (здесь говорится именно что isinstance(int, object) есть True и это может сильно запутать потому что на самом деле это не так, но из-за того, что метакласс type является классом и наследуется от object, а isinstance проверяет подклассы, проверка возвращает True), то значит можно создать int через объект object. Подобно тому как object создается через type. Но при этом isinstance(type, object) тоже True, а на самом деле False. Хотя если дословно переводить название функции (является экземпляром/есть экземпляр) может показаться, что object есть метакласс над самым "верхним" метаклассом type. Это происходит из описанного выше запутанного наследования в Python.

‎Логика примерно такая: если int порождает объекты целого типа (например 3), то isinstance(3, int) возвращает True, если что-то порождает int (это метакласс type), то isinstance(int, type) возвращает True, но object не является метаклассом. Так почему isinstance(int, object) возвращает True, ведь object не создает int, а просто наследует.

‎Эта запутанность возникает если не знать про то, что type подкласс object (об этом не говорит переменная mro когда вызывается для класса int, потому что как классы они на одной ступени, но как экземпляр int ниже type). То есть не учитывается существование промежуточного метакласса type. Поэтому я составил следующую иерархию для лучшего понимания. Не уверен, что вторую иерархию можно так изобразить. Но чтобы усвоить, что я имею в виду, лучше взглянуть.

‎Когда рассматриваем классы (кто кого наследует):

‎object

‎|

‎+--> type, int, … (все собственные и встроенные типы данных)

‎Объяснить с

‎Я для простоты написал все типы данных, понятно, что можно выделить больше ступеней, например, int наследует bool, но для понимания достаточно этого

‎Когда рассматриваем экземпляры (кто кого инстанцирует):

‎type

‎|

‎+--> Метаклассы (не обязательная ступень)

‎ |

‎ +--> int, str, … (все собственные и встроенные типы данных)

‎ |

· ‎--> объекты целого типа, строки, …

‎Объяснить с

‎При этом будет ошибкой как в наследовании сказать, что type порождает объекты целого типа (если проводить аналогию с классами, то object родительский класс для любого подкласса, даже если он имеет посредников). Поэтому эта схема довольно условная. isinstance(3, type) есть False так как у type нет потомков классов (он может наследовать только метаклассы) и isinstance просто не найдет класс int, который и создает объекты целого типа.



‎надеюсь данная информация смогла вам как то помочь.

Report Page