- Для чего нужны метаклассы python?
- Использование метакласса в Python
- Динамическое создание классов
- Создание собственных метаклассов
- Выводы
- Метаклассы в Python
- В Python все является объектом
- Классы в Python можно создавать динамически
- Создание пользовательских метаклассов
- __new__ и __init__
- Метод метакласса __call__
- Метод метакласса __prepare__
- Проектирование Singleton с использованием метакласса
- Заключение
Для чего нужны метаклассы python?
Метакласс — это класс класса. Всякий раз, когда создается экземпляр класса (объекта), поведение объекта определяется классом. Метакласс определяет поведение самого класса.
Использование метакласса в Python
Причина использования метаклассов заключается в том, что классы Python сами являются объектами. Поскольку классы являются объектами, мы можем выполнять с ними различные операции, такие как присвоение их переменной, копирование и т. Д.
А поскольку они являются объектами, мы можем создавать их динамически, как и любой другой объект.
Чтобы лучше понять концепцию метаклассов, мы сначала посмотрим, как Python определяет классы. Язык определяет все как объект, будь то int , string или что-то еще.
Если вы помните, чтобы посмотреть на тип любого объекта Python, мы используем функцию type .
>>> print(type(123)) >>> print(type([1, 2, 3])) >>> class A(): . def __init__(self): . pass . >>> a = A() >>> print(type(a))
Как мы видим, он возвращает class для каждого случая. Но чтобы увидеть, как Python определяет сам класс, мы просто посмотрим на его тип.
>>> print(type(type(123)) >>> print(type(A))
Как видите, тип ( class ) — это type класса! Получается, что класс определяется самим классом? Что это за явление?
Это концепция метакласса, который служит для определения других классов. По сути, это фабрика классов, из которой могут быть определены другие классы, такие как int s и str s.
type — это метакласс, который язык использует для создания объекта. (поэтому у каждого объекта есть тип)
А поскольку type — это метакласс, мы можем создавать из него другие классы.
Динамическое создание классов
Мы можем создавать классы динамически путем создания экземпляра из конструктора type(name, bases, attr) : type(name, bases, attr)
- name -> имя класса
- базы -> классы, от которых наследуется новый класс
- attr -> словарь атрибутов + методы, содержащиеся в классе
>>> Animal = type('Animal',(), dict(__init__ = lambda self: None, worth = lambda self, value: value))
class Animal(): def __init__(self): pass def worth(self, value): return value
Первый фрагмент кода написать намного проще, чем второй. Написание тела класса даже во время динамического объявления не обеспечивает большой гибкости.
Следовательно, метаклассы предоставляют мощный и простой способ динамического создания новых классов.
Создание собственных метаклассов
Чтобы создать наш собственный метакласс, нам нужно унаследовать существующий метакласс `type` и переопределить некоторые специальные методы:
- __new __() -> Это вызывается перед __init__() . Он отвечает за создание объекта и возвращает его.
- __init __() -> Это для инициализации вновь созданного объекта, который передается как параметр (параметр self )
В следующем фрагменте показано, как можно создать метакласс:
class MyMetaclass(type): def __new__(cls, name, bases, dict): print('Creating a new object of', name) # Invoke __new__() method of the metaclass type return super(MyMetaclass, cls).__new__(cls, name, bases, dict) def __init__(cls, name, bases, dict): print('Initialising class', name) super(MyMetaclass, cls).__init__(name, bases, dict)
Теперь, когда мы создали наш собственный метакласс, нам нужно убедиться, что мы создаем другие классы, которые используют наш метакласс.
Для этого мы передаем параметр metaclass в определение нового класса, который сообщает классу использовать наш собственный метакласс в качестве собственного метакласса, а не type .
class Student(metaclass=MyMetaclass): def __init__(self, name): self.name = name def get_name(self): return self.name
Здесь Student использует MyMetaclass как свой метакласс. Следовательно, при создании экземпляра Student будут вызываться наши собственные методы метакласса вместо метакласса type .
stud = Student('Amit') print(stud.get_name()) print('Type of Student object:', type(stud)) print('Type of Student Class:', type(Student))
Creating a new object of Student Initialising class Student Amit Type of Student object: Type of Student Class:
В старых версиях Python 2.7 или ниже используется ключевое слово __metaclass__ для указания используемого метакласса. Python3 изменил это поведение, чтобы передать metaclass в качестве параметра.
Выводы
Хотя метаклассы служат очень мощным способом создания пользовательских API-интерфейсов и определения их поведения во время создания объектов и классов, они очень редко когда-либо используются на практике, поскольку для них есть другие методы обхода. Еще по теме можете почитать на stackoverflow.com.
Метаклассы в Python
В этом руководстве мы расскажем, что такое метаклассы в Python, зачем они нужны и как их создавать.
Метакласс в Python — это класс классов, определяющий поведение класса. То есть класс сам по себе является экземпляром метакласса. Класс определяет поведение экземпляров этого класса. Чтобы хорошо понимать метаклассы, необходимо иметь предыдущий опыт работы с классами в Python. Поэтому, прежде чем углубиться в метаклассы, давайте рассмотрим несколько основных концепций.
От редакции Pythonist. О классах можно почитать в статье «Классы в Python».
В Python все является объектом
class TestClass(): pass my_test_class = TestClass() print(my_test_class) # Oytput: #
Классы в Python можно создавать динамически
Функция type() в Python позволяет нам определить тип объекта. Давайте проверим тип объекта, который мы только что создали:
type(TestClass) # type type(type) # type
Подождите, но что это было? Мы ожидали, что тип объекта, который мы создали выше, будет классом, однако это не так. Зафиксируйте, пожалуйста, эту мысль. Мы вернемся к ней чуть позже.
Кроме того, можно заметить, что type имеет тип type . Всё потому, что это экземпляр type .
Функция type , помимо определения типа объектов, имеет еще один волшебный вариант применения. С ее помощью можно динамически создавать классы. Давайте рассмотрим, как это делается.
Показанный ниже класс Pythonist будет создан с использованием type :
class Pythonist(): pass PythonistClass = type(‘Pythonist’, (), <>) print(PythonistClass) print(Pythonist()) # Output: # #
Здесь Pythonist — это имя класса, а Pythonist Class — это переменная, содержащая ссылку на класс.
При использовании type мы можем передавать атрибуты класса с помощью словаря:
PythonClass = type(‘PythonClass’, (), ) print(PythonClass.start_date, PythonClass.instructor) print(PythonClass) # Output: # August 2018 John Doe #
Если мы хотим, чтобы наш PythonClass наследовался от класса Pythonist , мы передаем его нашему второму аргументу при определении класса с использованием type :
PythonClass = type(‘PythonClass’, (Pythonist,), ) print(PythonClass) # Output: #
Теперь, когда эти две концепции ясны, мы понимаем, что Python создает классы, используя метакласс. Мы видели, что все в Python является объектом и эти объекты создаются метаклассами.
Всякий раз, когда мы вызываем class для создания класса, есть метакласс, который колдует над созданием класса за кулисами. Мы уже видели, как это делает type . Это похоже на str , который создает строки, и int , который создает целые числа. В Python атрибут __class__ позволяет нам проверить тип текущего экземпляра. Давайте создадим строку и проверим ее тип.
article = 'metaclasses' article.__class__ # str
Мы также можем проверить тип, используя type(article) следующим образом:
Проверив тип самого str , мы узнаем, что это тоже type :
Если мы проверим тип float , int , list , tuple и dict с помощью type() , мы получим аналогичный результат. Это потому, что все эти объекты имеют тип type :
print(type(list),type(float), type(dict), type(tuple)) #
Итак, мы уже видели, как type создает классы. Следовательно, когда мы проверяем __class__ из __class__ , он должен возвращать type :
article.__class__.__class__ # type
Создание пользовательских метаклассов
В Python мы можем настроить процесс создания класса, передав ключевое слово metaclass в определение класса. Это также можно сделать, унаследовав класс, в который уже передали это ключевое слово. К примеру, это может выглядеть следующим образом:
class MyMeta(type): pass class MyClass(metaclass=MyMeta): pass class MySubclass(MyClass): pass
Ниже мы видим, что тип класса MyMeta — type , а тип MyClass и MySubClass — MyMeta .
print(type(MyMeta)) print(type(MyClass)) print(type(MySubclass)) # Output: # # #
При определении класса и отсутствии метакласса по умолчанию будет использоваться метакласс type . Если задан метакласс, не являющийся экземпляром type() , то он используется непосредственно как метакласс.
__new__ и __init__
Метаклассы также могут быть определены одним из двух способов, показанных ниже. Давайте рассмотрим разницу между ними:
class MetaOne(type): def __new__(cls, name, bases, dict): pass class MetaTwo(type): def __init__(self, name, bases, dict): pass
__new__ используется, когда нужно определить кортежи dict или base перед созданием класса. Возвращаемое значение __new__ обычно является экземпляром cls . __new__ позволяет подклассам неизменяемых типов настраивать создание экземпляров. Его можно переопределить в пользовательских метаклассах, чтобы настроить создание класса.
__init__ обычно вызывается после создания объекта для его инициализации.
Метод метакласса __call__
Согласно официальной документации, мы также можем переопределить другие методы класса. К примеру, определив собственный метод __call__() в метаклассе, что позволит настроить поведение при вызове класса.
Метод метакласса __prepare__
«После определения соответствующего метакласса подготавливается пространство имен класса.
Если у метакласса есть атрибут __prepare__ , он вызывается как namespace = metaclass.__prepare__(name, bases, **kwds) (где дополнительные аргументы ключевого слова, если они есть, берутся из определения класса).
Если метакласс не имеет __prepare__attribute , то пространство имен класса инициализируется как пустое упорядоченное отображение».
Проектирование Singleton с использованием метакласса
Данный шаблон проектирования ограничивает создание экземпляра класса только одним объектом. Это может оказаться полезным, например, при разработке класса для подключения к базе данных. Возможно, вы захотите иметь только один экземпляр класса соединения.
class SingletonMeta(type): _instances = <> def __call__(cls, *args, **kwargs): if cls not in cls._instances: cls._instances[cls] = super(SingletonMeta,cls).__call__(*args, **kwargs) return cls._instances[cls] class SingletonClass(metaclass=SingletonMeta): pass
Заключение
В этой статье мы узнали о том, что такое метаклассы в Python и как мы можем реализовать их в нашем коде. Метаклассы могут применяться, среди прочего, для ведения журнала, регистрации классов во время создания и профилирования. Они кажутся довольно абстрактными понятиями, и у вас может возникнуть сомнение относительно целесообразности их использования. Лучше всего об этом сказал Тим Питерс, питонист с большим опытом:
«Метаклассы — это более глубокая магия, о которой 99% пользователей не должны беспокоиться. Если вы задаетесь вопросом, нужны ли они вам – значит, нет, вы в них не нуждаетесь (люди, которые действительно нуждаются в метаклассах, точно знают, что они им нужны, и не нуждаются в объяснении зачем)».
Надеемся, данная статья была вам полезна! Успехов в написании кода!