example - метаклассы python это
Что такое метаклассы в Python? (10)
Что такое метаклассы? Для чего вы их используете?
TLDR: метаклас создает экземпляр и определяет поведение для класса, как экземпляр класса, и определяет поведение для экземпляра.
псевдокод:
>>> Class(...)
instance
Вышеприведенное должно выглядеть знакомым. Ну, откуда появился Class
? Это пример метакласса (также псевдокод):
>>> Metaclass(...)
Class
В реальном коде мы можем передать метакласс по умолчанию, type
, все, что нам нужно, чтобы создать экземпляр класса, и мы получим класс:
>>> type('Foo', (object,), {}) # requires a name, bases, and a namespace
<class '__main__.Foo'>
Положив это по-другому
Класс относится к экземпляру, поскольку метакласс относится к классу.
Когда мы создаем экземпляр объекта, мы получаем экземпляр:
>>> object() # instantiation of class <object object at 0x7f9069b4e0b0> # instance
Аналогично, когда мы определяем класс явно с метаклассом по умолчанию,
type
его:>>> type('Object', (object,), {}) # instantiation of metaclass <class '__main__.Object'> # instance
Другими словами, класс является экземпляром метакласса:
>>> isinstance(object, type) True
Положите третий способ, метакласс - класс класса.
>>> type(object) == type True >>> object.__class__ <class 'type'>
Когда вы пишете определение класса, и Python выполняет его, он использует метакласс для создания экземпляра объекта класса (который, в свою очередь, будет использоваться для создания экземпляров этого класса).
Так же, как мы можем использовать определения классов для изменения поведения пользовательских экземпляров объектов, мы можем использовать определение класса метакласса, чтобы изменить способ поведения объекта класса.
Для чего их можно использовать? Из docs :
Потенциальное использование метаклассов безгранично. Некоторые идеи, которые были изучены, включают в себя ведение журнала, проверку интерфейса, автоматическое делегирование, автоматическое создание свойств, прокси, фреймворки и автоматическую блокировку / синхронизацию ресурсов.
Тем не менее, как правило, пользователям рекомендуется избегать использования метаклассов, если это абсолютно необходимо.
Вы используете метакласс каждый раз, когда вы создаете класс:
Например, когда вы пишете определение класса, например,
class Foo(object):
'demo'
Вы создаете экземпляр объекта класса.
>>> Foo
<class '__main__.Foo'>
>>> isinstance(Foo, type), isinstance(Foo, object)
(True, True)
Это то же самое, что и функционально вызывающий type
с соответствующими аргументами и присваивание результата переменной этого имени:
name = 'Foo'
bases = (object,)
namespace = {'__doc__': 'demo'}
Foo = type(name, bases, namespace)
Обратите внимание: некоторые вещи автоматически добавляются в __dict__
, т. __dict__
имен:
>>> Foo.__dict__
dict_proxy({'__dict__': <attribute '__dict__' of 'Foo' objects>,
'__module__': '__main__', '__weakref__': <attribute '__weakref__'
of 'Foo' objects>, '__doc__': 'demo'})
Метаклассом объекта, который мы создали, в обоих случаях является type
.
(Замечание по содержимому класса __dict__
: __module__
существует, потому что классы должны знать, где они определены, и __dict__
и __weakref__
есть, потому что мы не определяем __slots__
- если мы определим __slots__
мы сохраним немного в случаях, поскольку мы можем запретить __dict__
и __weakref__
, исключив их. Например:
>>> Baz = type('Bar', (object,), {'__doc__': 'demo', '__slots__': ()})
>>> Baz.__dict__
mappingproxy({'__doc__': 'demo', '__slots__': (), '__module__': '__main__'})
... но я отвлекся.)
Мы можем расширить type
как и любое другое определение класса:
Вот __repr__
классов по умолчанию __repr__
:
>>> Foo
<class '__main__.Foo'>
Одной из самых ценных вещей, которые мы можем сделать по умолчанию при написании объекта Python, является предоставление ему хорошего __repr__
. Когда мы вызываем help(repr)
мы узнаем, что есть хороший тест для __repr__
который также требует проверки на равенство - obj == eval(repr(obj))
. Следующая простая реализация __repr__
и __eq__
для экземпляров классов нашего класса классов предоставляет нам демонстрацию, которая может улучшить класс по умолчанию __repr__
:
class Type(type):
def __repr__(cls):
"""
>>> Baz
Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
>>> eval(repr(Baz))
Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
"""
metaname = type(cls).__name__
name = cls.__name__
parents = ', '.join(b.__name__ for b in cls.__bases__)
if parents:
parents += ','
namespace = ', '.join(': '.join(
(repr(k), repr(v) if not isinstance(v, type) else v.__name__))
for k, v in cls.__dict__.items())
return '{0}(\'{1}\', ({2}), {{{3}}})'.format(metaname, name, parents, namespace)
def __eq__(cls, other):
"""
>>> Baz == eval(repr(Baz))
True
"""
return (cls.__name__, cls.__bases__, cls.__dict__) == (
other.__name__, other.__bases__, other.__dict__)
Итак, теперь, когда мы создаем объект с этим метаклассом, __repr__
эхо в командной строке обеспечивает гораздо менее уродливое зрелище, чем значение по умолчанию:
>>> class Bar(object): pass
>>> Baz = Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
>>> Baz
Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
С хорошим, __repr__
определенным для экземпляра класса, у нас есть более сильная способность отлаживать наш код. Тем не менее, дальнейшая проверка с eval(repr(Class))
вероятностью маловероятна (поскольку функциям было бы совершенно невозможно оценить их дефолт __repr__
).
Ожидаемое использование: __prepare__
пространство имен
Если, например, мы хотим знать, в каком порядке создаются методы класса, мы могли бы предоставить упорядоченный dict как пространство имен класса. Мы сделали бы это, с помощью __prepare__
чего пространство имен dict для класса, если оно реализовано в Python 3 :
from collections import OrderedDict
class OrderedType(Type):
@classmethod
def __prepare__(metacls, name, bases, **kwargs):
return OrderedDict()
def __new__(cls, name, bases, namespace, **kwargs):
result = Type.__new__(cls, name, bases, dict(namespace))
result.members = tuple(namespace)
return result
И использование:
class OrderedMethodsObject(object, metaclass=OrderedType):
def method1(self): pass
def method2(self): pass
def method3(self): pass
def method4(self): pass
И теперь у нас есть запись порядка, в котором были созданы эти методы (и другие атрибуты класса):
>>> OrderedMethodsObject.members
('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4')
Обратите внимание, что этот пример был адаптирован из docs - это новое перечисление в стандартной библиотеке .
Так что мы сделали, это создать метакласс, создав класс. Мы также можем рассматривать метакласс как и любой другой класс. Он имеет порядок разрешения метода:
>>> inspect.getmro(OrderedType)
(<class '__main__.OrderedType'>, <class '__main__.Type'>, <class 'type'>, <class 'object'>)
И он имеет приблизительно правильные значения repr
(которые мы больше не можем использовать, если мы не сможем найти способ представлять наши функции.):
>>> OrderedMethodsObject
OrderedType('OrderedMethodsObject', (object,), {'method1': <function OrderedMethodsObject.method1 at 0x0000000002DB01E0>, 'members': ('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4'), 'method3': <function OrderedMet
hodsObject.method3 at 0x0000000002DB02F0>, 'method2': <function OrderedMethodsObject.method2 at 0x0000000002DB0268>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'OrderedMethodsObject' objects>, '__doc__': None, '__d
ict__': <attribute '__dict__' of 'OrderedMethodsObject' objects>, 'method4': <function OrderedMethodsObject.method4 at 0x0000000002DB0378>})
Что такое метаклассы и для чего мы их используем?
Классы как объекты
Прежде чем понимать метаклассы, вам нужно освоить классы в Python. И Python имеет очень своеобразное представление о том, какие классы, заимствованные из языка Smalltalk.
В большинстве языков классы - это всего лишь фрагменты кода, описывающие процесс создания объекта. Это тоже верно в Python:
>>> class ObjectCreator(object):
... pass
...
>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>
Но классы больше, чем в Python. Классы тоже объекты.
Да, объекты.
Как только вы используете class
ключевого слова, Python выполняет его и создает OBJECT. Инструкция
>>> class ObjectCreator(object):
... pass
...
создает в памяти объект с именем «ObjectCreator».
Этот объект (класс) сам способен создавать объекты (экземпляры), и именно поэтому это класс .
Но все же это объект, и поэтому:
- вы можете назначить его переменной
- вы можете его скопировать
- вы можете добавить к нему атрибуты
- вы можете передать его как параметр функции
например:
>>> print(ObjectCreator) # you can print a class because it's an object
<class '__main__.ObjectCreator'>
>>> def echo(o):
... print(o)
...
>>> echo(ObjectCreator) # you can pass a class as a parameter
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>
Динамическое создание классов
Поскольку классы являются объектами, вы можете создавать их «на лету», как и любой объект.
Во-первых, вы можете создать класс в функции с помощью class
:
>>> def choose_class(name):
... if name == 'foo':
... class Foo(object):
... pass
... return Foo # return the class, not an instance
... else:
... class Bar(object):
... pass
... return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # the function returns a class, not an instance
<class '__main__.Foo'>
>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>
Но это не так динамично, так как вам все равно придется писать весь класс самостоятельно.
Поскольку классы являются объектами, они должны быть сгенерированы чем-то.
Когда вы используете ключевое слово class
, Python автоматически создает этот объект. Но, как и большинство вещей в Python, он дает вам способ сделать это вручную.
Помните type
функции? Хорошая старая функция, которая позволяет вам узнать, какой тип объекта:
>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>
Ну, type
имеет совершенно разные возможности, он также может создавать классы «на лету». type
может взять описание класса в качестве параметров и вернуть класс.
(Я знаю, глупо, что одна и та же функция может иметь два совершенно разных применения в соответствии с параметрами, которые вы передаете ему. Это проблема из-за обратной совместимости в Python)
type
работает таким образом:
type(name of the class,
tuple of the parent class (for inheritance, can be empty),
dictionary containing attributes names and values)
например:
>>> class MyShinyClass(object):
... pass
могут быть созданы вручную таким образом:
>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # create an instance with the class
<__main__.MyShinyClass object at 0x8997cec>
Вы заметите, что мы используем «MyShinyClass» как имя класса и как переменную для хранения ссылки на класс. Они могут быть разными, но нет причин усложнять ситуацию.
type
принимает словарь для определения атрибутов класса. Так:
>>> class Foo(object):
... bar = True
Можно перевести на:
>>> Foo = type('Foo', (), {'bar':True})
И используется как обычный класс:
>>> print(Foo)
<class '__main__.Foo'>
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar)
True
И, конечно, вы можете унаследовать от него, так что:
>>> class FooChild(Foo):
... pass
было бы:
>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # bar is inherited from Foo
True
В конце концов вы захотите добавить методы в свой класс. Просто определите функцию с соответствующей сигнатурой и назначьте ее как атрибут.
>>> def echo_bar(self):
... print(self.bar)
...
>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
>>> hasattr(Foo, 'echo_bar')
False
>>> hasattr(FooChild, 'echo_bar')
True
>>> my_foo = FooChild()
>>> my_foo.echo_bar()
True
И вы можете добавить еще больше методов после динамического создания класса, так же, как добавлять методы к обычно создаваемому объекту класса.
>>> def echo_bar_more(self):
... print('yet another method')
...
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
True
Вы видите, куда мы идем: в Python классы - это объекты, и вы можете динамически создавать класс «на лету».
Это то, что делает Python, когда вы используете class
ключевого слова, и делает это, используя метакласс.
Что такое метаклассы (наконец)
Метаклассы - это «материал», который создает классы.
Вы определяете классы для создания объектов, не так ли?
Но мы узнали, что классы Python - это объекты.
Ну, метаклассы - вот что создает эти объекты. Это классы классов, вы можете представить их следующим образом:
MyClass = MetaClass()
my_object = MyClass()
Вы видели, что этот type
позволяет вам сделать что-то вроде этого:
MyClass = type('MyClass', (), {})
Это потому, что type
функции на самом деле является метаклассом. type
- это метакласс Python, используемый для создания всех классов за кулисами.
Теперь вы задаетесь вопросом, почему это написано в нижнем регистре, а не в Type
?
Ну, я думаю, это вопрос согласованности с str
, класс, который создает объекты строк, и int
класс, который создает целые объекты. type
- это просто класс, который создает объекты класса.
Вы видите это, проверяя атрибут __class__
.
Все, и я имею в виду все, - это объект в Python. Это включает в себя ints, строки, функции и классы. Все они - объекты. И все они созданы из класса:
>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>
Теперь, что такое __class__
любого __class__
?
>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>
Итак, метакласс - это всего лишь материал, создающий объекты класса.
Вы можете назвать это «фабрикой классов», если хотите.
type
- это встроенный метакласс, используемый Python, но, конечно, вы можете создать свой собственный метакласс.
__metaclass__
В Python 2 вы можете добавить атрибут __metaclass__
при написании класса (см. Следующий раздел для синтаксиса Python 3):
class Foo(object):
__metaclass__ = something...
[...]
Если вы это сделаете, Python будет использовать метакласс для создания класса Foo
.
Осторожно, это сложно.
Сначала вы пишете class Foo(object)
, но объект класса Foo
еще не создан в памяти.
Python будет искать __metaclass__
в определении класса. Если он найдет его, он будет использовать его для создания класса объекта Foo
. Если это не так, он будет использовать type
для создания класса.
Прочитайте это несколько раз.
Когда вы выполните:
class Foo(Bar):
pass
Python делает следующее:
В Foo
есть атрибут __metaclass__
?
Если да, создайте в памяти объект класса (я сказал объект класса, оставайтесь со мной здесь), с именем Foo
, используя то, что находится в __metaclass__
.
Если Python не может найти __metaclass__
, он будет искать __metaclass__
на уровне MODULE и попытаться сделать то же самое (но только для классов, которые не наследуют ничего, в основном классы старого стиля).
Тогда, если он вообще не может найти какой-либо __metaclass__
, он будет использовать собственный метакласс класса (первый родительский) (который может быть type
по умолчанию) для создания объекта класса.
Будьте осторожны, __metaclass__
атрибут __metaclass__
не был унаследован, метаклассом родителя ( Bar.__class__
) будет. Если в Bar
используется атрибут __metaclass__
который создал Bar
с type()
(а не type.__new__()
), подклассы не наследуют этого поведения.
Теперь большой вопрос: что вы можете положить в __metaclass__
?
Ответ: то, что может создать класс.
А что может создать класс? type
или что-либо, что подклассы или использует его.
Метаклассы в Python 3
Синтаксис для установки метакласса был изменен в Python 3:
class Foo(object, metaclass=something):
[...]
т.е. атрибут __metaclass__
больше не используется, в пользу аргумента ключевого слова в списке базовых классов.
Однако поведение метаклассов остается в основном одинаковым .
Пользовательские метаклассы
Основная цель метакласса - автоматически менять класс, когда он создается.
Обычно вы делаете это для API, где вы хотите создавать классы, соответствующие текущему контексту.
Представьте себе глупый пример, в котором вы решите, что все классы в вашем модуле должны иметь свои атрибуты, написанные в верхнем регистре. Существует несколько способов сделать это, но один из способов - установить __metaclass__
на уровне модуля.
Таким образом, все классы этого модуля будут созданы с использованием этого метакласса, и мы просто должны сообщить метаклассу, чтобы все атрибуты были в верхнем регистре.
К счастью, __metaclass__
действительно может быть любым вызываемым, он не должен быть формальным классом (я знаю, что-то с «классом» в его названии не обязательно должен быть классом, идите в цифру ... но это полезно).
Итак, мы начнем с простого примера, используя функцию.
# the metaclass will automatically get passed the same argument
# that you usually pass to `type`
def upper_attr(future_class_name, future_class_parents, future_class_attr):
"""
Return a class object, with the list of its attribute turned
into uppercase.
"""
# pick up any attribute that doesn't start with '__' and uppercase it
uppercase_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
# let `type` do the class creation
return type(future_class_name, future_class_parents, uppercase_attr)
__metaclass__ = upper_attr # this will affect all classes in the module
class Foo(): # global __metaclass__ won't work with "object" though
# but we can define __metaclass__ here instead to affect only this class
# and this will work with "object" children
bar = 'bip'
print(hasattr(Foo, 'bar'))
# Out: False
print(hasattr(Foo, 'BAR'))
# Out: True
f = Foo()
print(f.BAR)
# Out: 'bip'
Теперь давайте сделаем то же самое, но с использованием реального класса для метакласса:
# remember that `type` is actually a class like `str` and `int`
# so you can inherit from it
class UpperAttrMetaclass(type):
# __new__ is the method called before __init__
# it's the method that creates the object and returns it
# while __init__ just initializes the object passed as parameter
# you rarely use __new__, except when you want to control how the object
# is created.
# here the created object is the class, and we want to customize it
# so we override __new__
# you can do some stuff in __init__ too if you wish
# some advanced use involves overriding __call__ as well, but we won't
# see this
def __new__(upperattr_metaclass, future_class_name,
future_class_parents, future_class_attr):
uppercase_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return type(future_class_name, future_class_parents, uppercase_attr)
Но это не ООП. Мы вызываем type
напрямую, и мы не переопределяем и не вызываем родительский __new__
. Давай сделаем это:
class UpperAttrMetaclass(type):
def __new__(upperattr_metaclass, future_class_name,
future_class_parents, future_class_attr):
uppercase_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
# reuse the type.__new__ method
# this is basic OOP, nothing magic in there
return type.__new__(upperattr_metaclass, future_class_name,
future_class_parents, uppercase_attr)
Возможно, вы заметили дополнительный аргумент upperattr_metaclass
. В этом нет ничего особенного: __new__
всегда получает класс, в котором он определен, в качестве первого параметра. Так же, как и вы, для обычных методов, которые получают экземпляр как первый параметр или определяющий класс для методов класса.
Конечно, имена, которые я использовал здесь, являются длинными для ясности, но, как и для self
, все аргументы имеют обычные имена. Итак, настоящий метакласс будет выглядеть так:
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, dct):
uppercase_attr = {}
for name, val in dct.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return type.__new__(cls, clsname, bases, uppercase_attr)
Мы можем сделать его еще более чистым, используя super
, что облегчит наследование (потому что да, у вас могут быть метаклассы, наследующие от метаклассов, наследующие от типа):
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, dct):
uppercase_attr = {}
for name, val in dct.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)
Вот и все. В метаклассах ничего больше нет.
Причина сложности кода с использованием метаклассов заключается не в метаклассах, а потому, потому что вы обычно используете метаклассы, чтобы делать скрученные вещи, полагаясь на интроспекцию, манипулируя наследованием, vars, такие как __dict__
и т. Д.
Действительно, метаклассы особенно полезны для черной магии и, следовательно, для сложных вещей. Но сами по себе они просты:
- перехватывать создание класса
- изменить класс
- вернуть измененный класс
Зачем использовать классы метаклассов вместо функций?
Поскольку __metaclass__
может принимать любые вызываемые вызовы, почему вы используете класс, так как это явно сложнее?
Этому есть несколько причин:
- Намерение ясно. Когда вы читаете
UpperAttrMetaclass(type)
, вы знаете, что будет следовать - Вы можете использовать ООП. Metaclass может наследовать от метакласса, переопределять родительские методы. Метаклассы могут даже использовать метаклассы.
- Подклассы класса будут экземплярами метакласса, если вы указали класс метакласса, но не с метаклассовой функцией.
- Вы можете лучше структурировать свой код. Вы никогда не используете метаклассы для чего-то столь же тривиального, как в приведенном выше примере. Это обычно для чего-то сложного. Наличие возможности делать несколько методов и группировать их в одном классе очень полезно, чтобы сделать код более удобным для чтения.
- Вы можете подключаться к
__new__
,__init__
и__call__
. Это позволит вам делать разные вещи. Даже если вы обычно можете делать все это в__new__
, некоторые люди просто удобнее использовать__init__
. - Они называются метаклассами, черт побери! Это должно что-то значить!
Зачем использовать метаклассы?
Теперь большой вопрос. Почему вы используете какую-то непонятную функцию, подверженную ошибкам?
Ну, обычно у вас нет:
Метаклассы - это более глубокая магия, о которой 99% пользователей не должны беспокоиться. Если вы задаетесь вопросом, нужны ли вам они, вы не делаете (люди, которые действительно нуждаются в них, знают с уверенностью, что они в них нуждаются, и не нуждаются в объяснении о том, почему).
Python Guru Тим Петерс
Основным вариантом использования метакласса является создание API. Типичным примером этого является ORM Django.
Это позволяет вам определить что-то вроде этого:
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()
Но если вы это сделаете:
guy = Person(name='bob', age='35')
print(guy.age)
Он не вернет объект IntegerField
. Он вернет int
и может даже взять его непосредственно из базы данных.
Это возможно, потому что models.Model
определяет __metaclass__
и использует некоторую магию, которая превратит Person
вы только что определили с помощью простых операторов, в сложный крючок в поле базы данных.
Django делает что-то сложное, просто выставляя простой API и используя метаклассы, воссоздавая код из этого API, чтобы выполнять реальную работу за кулисами.
Последнее слово
Во-первых, вы знаете, что классы - это объекты, которые могут создавать экземпляры.
На самом деле классы сами по себе являются экземплярами. Из метаклассов.
>>> class Foo(object): pass
>>> id(Foo)
142630324
Все это объект в Python, и все они - либо экземпляры классов, либо экземпляры метаклассов.
За исключением type
.
type
на самом деле является его собственным метаклассом. Это не то, что можно воспроизвести в чистом Python, и это делается путем небольшого обмана на уровне реализации.
Во-вторых, метаклассы сложны. Возможно, вы не захотите использовать их для очень простых изменений класса. Вы можете менять классы, используя два разных метода:
- исправление обезьяны
- декораторы класса
В 99% случаев, когда вам требуется изменение класса, вам лучше использовать их.
Но в 98% случаев вам вообще не нужны изменения класса.
Версия tl; dr
type(obj)
Функция получает вас тип объекта.
Класс type()
представляет собой его метакласс .
Чтобы использовать метакласс:
class Foo(object):
__metaclass__ = MyMetaClass
Другие объяснили, как работают метаклассы и как они вписываются в систему типа Python. Вот пример того, для чего их можно использовать. В рамках тестирования, который я написал, я хотел отслеживать порядок, в котором были определены классы, чтобы впоследствии описать их в этом порядке. Мне было проще сделать это с помощью метакласса.
class MyMeta(type):
counter = 0
def __init__(cls, name, bases, dic):
type.__init__(cls, name, bases, dic)
cls._order = MyMeta.counter
MyMeta.counter += 1
class MyType(object): # Python 2
__metaclass__ = MyMeta
class MyType(metaclass=MyMeta): # Python 3
pass
Все, что является подклассом MyType
затем получает атрибут класса _order
который записывает порядок, в котором были определены классы.
Одно использование для метаклассов - это добавление новых свойств и методов в экземпляр автоматически.
Например, если вы посмотрите на модели Django , их определение выглядит немного запутанным. Похоже, что вы определяете только свойства класса:
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
Однако во время выполнения объекты Person заполняются всякими полезными методами. Посмотрите source какой-то удивительной метаклассификации.
Я думаю, что введение ONLamp в программирование метакласса хорошо написано и дает действительно хорошее введение в эту тему, несмотря на то, что уже несколько лет.
http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html (заархивировано по адресу https://web.archive.org/web/20080206005253/http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html )
Короче: класс - это проект создания экземпляра, метакласс - это план создания класса. Легко видеть, что в классах Python тоже должны быть первоклассными объектами, чтобы включить это поведение.
Я никогда не писал его сам, но я думаю, что одно из лучших применений метаклассов можно увидеть в структуре Django . Классы моделей используют метаклассический подход, чтобы включить декларативный стиль написания новых моделей или классов классов. В то время как метакласс создает класс, все участники получают возможность настроить сам класс.
Остается сказать: если вы не знаете, что такое метаклассы, вероятность того, что они вам не понадобятся, составляет 99%.
Обратите внимание, этот ответ для Python 2.x, как он был написан в 2008 году, метаклассы немного отличаются в 3.x, см. Комментарии.
Метаклассы - это секретный соус, который делает работу «класса». Метакласс по умолчанию для нового объекта стиля называется «type».
class type(object)
| type(object) -> the object's type
| type(name, bases, dict) -> a new type
Метаклассы принимают 3 аргумента. ' name ', ' base ' и ' dict '
Здесь начинается секрет. Ищите, где в этом примере класса указано имя, базы и dict.
class ThisIsTheName(Bases, Are, Here):
All_the_code_here
def doesIs(create, a):
dict
Давайте определим метакласс, который продемонстрирует, как его вызывает класс .
def test_metaclass(name, bases, dict):
print 'The Class Name is', name
print 'The Class Bases are', bases
print 'The dict has', len(dict), 'elems, the keys are', dict.keys()
return "yellow"
class TestName(object, None, int, 1):
__metaclass__ = test_metaclass
foo = 1
def baz(self, arr):
pass
print 'TestName = ', repr(TestName)
# output =>
The Class Name is TestName
The Class Bases are (<type 'object'>, None, <type 'int'>, 1)
The dict has 4 elems, the keys are ['baz', '__module__', 'foo', '__metaclass__']
TestName = 'yellow'
И теперь, пример, который на самом деле что-то означает, это автоматически сделает переменные в списке «атрибуты», установленными в классе, и установите значение «Нет».
def init_attributes(name, bases, dict):
if 'attributes' in dict:
for attr in dict['attributes']:
dict[attr] = None
return type(name, bases, dict)
class Initialised(object):
__metaclass__ = init_attributes
attributes = ['foo', 'bar', 'baz']
print 'foo =>', Initialised.foo
# output=>
foo => None
Обратите внимание, что магическое поведение, которое «Initalised» получает за счет наличия метакласса init_attributes
, не передается в подкласс Initalised.
Вот еще более конкретный пример, показывающий, как вы можете подклассифицировать «тип», чтобы создать метакласс, который выполняет действие при создании класса. Это довольно сложно:
class MetaSingleton(type):
instance = None
def __call__(cls, *args, **kw):
if cls.instance is None:
cls.instance = super(MetaSingleton, cls).__call__(*args, **kw)
return cls.instance
class Foo(object):
__metaclass__ = MetaSingleton
a = Foo()
b = Foo()
assert a is b
Обновление Python 3
Есть (на данный момент) два ключевых метода в метаклассе:
-
__prepare__
, а также -
__new__
__prepare__
позволяет вам OrderedDict
создавать настраиваемое сопоставление (например, an ), которое будет использоваться в качестве пространства имен во время создания класса. Вы должны вернуть экземпляр любого пространства имен, которое вы выберете. Если вы не реализуете __prepare__
нормальный dict
, используется.
__new__
отвечает за фактическое создание / модификацию окончательного класса.
Необычный, не имеющий ничего лишнего метакласса:
class Meta(type):
def __prepare__(metaclass, cls, bases):
return dict()
def __new__(metacls, cls, bases, clsdict):
return super().__new__(metacls, cls, bases, clsdict)
Простой пример:
Предположим, вы хотите, чтобы какой-то простой код проверки выполнялся по вашим атрибутам - например, он всегда должен быть a int
или a str
. Без метакласса ваш класс будет выглядеть примерно так:
class Person:
weight = ValidateType('weight', int)
age = ValidateType('age', int)
name = ValidateType('name', str)
Как вы можете видеть, вам нужно дважды повторить имя атрибута. Это делает опечатки возможными наряду с раздражающими ошибками.
Простой метакласс может решить эту проблему:
class Person(metaclass=Validator):
weight = ValidateType(int)
age = ValidateType(int)
name = ValidateType(str)
Это то, что метакласс будет выглядеть (не использовать, __prepare__
поскольку он не нужен):
class Validator(type):
def __new__(metacls, cls, bases, clsdict):
# search clsdict looking for ValidateType descriptors
for name, attr in clsdict.items():
if isinstance(attr, ValidateType):
attr.name = name
attr.attr = '_' + name
# create final class and return it
return super().__new__(metacls, cls, bases, clsdict)
Пример:
p = Person()
p.weight = 9
print(p.weight)
p.weight = '9'
производит:
9
Traceback (most recent call last):
File "simple_meta.py", line 36, in <module>
p.weight = '9'
File "simple_meta.py", line 24, in __set__
(self.name, self.type, value))
TypeError: weight must be of type(s) <class 'int'> (got '9')
Примечание . Этот пример достаточно прост, он также может быть выполнен с помощью декоратора класса, но, по-видимому, фактический метакласс будет делать гораздо больше.
Класс «ValidateType» для справки:
class ValidateType:
def __init__(self, type):
self.name = None # will be set by metaclass
self.attr = None # will be set by metaclass
self.type = type
def __get__(self, inst, cls):
if inst is None:
return self
else:
return inst.__dict__[self.attr]
def __set__(self, inst, value):
if not isinstance(value, self.type):
raise TypeError('%s must be of type(s) %s (got %r)' %
(self.name, self.type, value))
else:
inst.__dict__[self.attr] = value
Метакласс - это класс, который рассказывает, как (какой-то) класс должен быть создан.
Это случай, когда я рассматривал метакласс как решение моей проблемы: у меня была очень сложная проблема, которая, вероятно, могла быть решена по-другому, но я решил решить ее с помощью метакласса. Из-за сложности это один из немногих модулей, которые я написал, когда комментарии в модуле превосходят количество написанного кода. Вот...
#!/usr/bin/env python
# Copyright (C) 2013-2014 Craig Phillips. All rights reserved.
# This requires some explaining. The point of this metaclass excercise is to
# create a static abstract class that is in one way or another, dormant until
# queried. I experimented with creating a singlton on import, but that did
# not quite behave how I wanted it to. See now here, we are creating a class
# called GsyncOptions, that on import, will do nothing except state that its
# class creator is GsyncOptionsType. This means, docopt doesn't parse any
# of the help document, nor does it start processing command line options.
# So importing this module becomes really efficient. The complicated bit
# comes from requiring the GsyncOptions class to be static. By that, I mean
# any property on it, may or may not exist, since they are not statically
# defined; so I can't simply just define the class with a whole bunch of
# properties that are @property @staticmethods.
#
# So here's how it works:
#
# Executing 'from libgsync.options import GsyncOptions' does nothing more
# than load up this module, define the Type and the Class and import them
# into the callers namespace. Simple.
#
# Invoking 'GsyncOptions.debug' for the first time, or any other property
# causes the __metaclass__ __getattr__ method to be called, since the class
# is not instantiated as a class instance yet. The __getattr__ method on
# the type then initialises the class (GsyncOptions) via the __initialiseClass
# method. This is the first and only time the class will actually have its
# dictionary statically populated. The docopt module is invoked to parse the
# usage document and generate command line options from it. These are then
# paired with their defaults and what's in sys.argv. After all that, we
# setup some dynamic properties that could not be defined by their name in
# the usage, before everything is then transplanted onto the actual class
# object (or static class GsyncOptions).
#
# Another piece of magic, is to allow command line options to be set in
# in their native form and be translated into argparse style properties.
#
# Finally, the GsyncListOptions class is actually where the options are
# stored. This only acts as a mechanism for storing options as lists, to
# allow aggregation of duplicate options or options that can be specified
# multiple times. The __getattr__ call hides this by default, returning the
# last item in a property's list. However, if the entire list is required,
# calling the 'list()' method on the GsyncOptions class, returns a reference
# to the GsyncListOptions class, which contains all of the same properties
# but as lists and without the duplication of having them as both lists and
# static singlton values.
#
# So this actually means that GsyncOptions is actually a static proxy class...
#
# ...And all this is neatly hidden within a closure for safe keeping.
def GetGsyncOptionsType():
class GsyncListOptions(object):
__initialised = False
class GsyncOptionsType(type):
def __initialiseClass(cls):
if GsyncListOptions._GsyncListOptions__initialised: return
from docopt import docopt
from libgsync.options import doc
from libgsync import __version__
options = docopt(
doc.__doc__ % __version__,
version = __version__,
options_first = True
)
paths = options.pop('<path>', None)
setattr(cls, "destination_path", paths.pop() if paths else None)
setattr(cls, "source_paths", paths)
setattr(cls, "options", options)
for k, v in options.iteritems():
setattr(cls, k, v)
GsyncListOptions._GsyncListOptions__initialised = True
def list(cls):
return GsyncListOptions
def __getattr__(cls, name):
cls.__initialiseClass()
return getattr(GsyncListOptions, name)[-1]
def __setattr__(cls, name, value):
# Substitut option names: --an-option-name for an_option_name
import re
name = re.sub(r'^__', "", re.sub(r'-', "_", name))
listvalue = []
# Ensure value is converted to a list type for GsyncListOptions
if isinstance(value, list):
if value:
listvalue = [] + value
else:
listvalue = [ None ]
else:
listvalue = [ value ]
type.__setattr__(GsyncListOptions, name, listvalue)
# Cleanup this module to prevent tinkering.
import sys
module = sys.modules[__name__]
del module.__dict__['GetGsyncOptionsType']
return GsyncOptionsType
# Our singlton abstract proxy class.
class GsyncOptions(object):
__metaclass__ = GetGsyncOptionsType()
Функция type () может возвращать тип объекта или создавать новый тип,
например, мы можем создать класс Hi с помощью функции type () и не использовать этот способ с классом Hi (object):
def func(self, name='mike'):
print('Hi, %s.' % name)
Hi = type('Hi', (object,), dict(hi=func))
h = Hi()
h.hi()
Hi, mike.
type(Hi)
type
type(h)
__main__.Hi
В дополнение к типу () для динамического создания классов вы можете контролировать поведение класса и использовать метакласс.
Согласно объектной модели Python, класс является объектом, поэтому класс должен быть экземпляром другого определенного класса. По умолчанию класс Python является экземпляром класса type. То есть, тип - это метакласс большинства встроенных классов и метакласс пользовательских классов.
class ListMetaclass(type):
def __new__(cls, name, bases, attrs):
attrs['add'] = lambda self, value: self.append(value)
return type.__new__(cls, name, bases, attrs)
class CustomList(list, metaclass=ListMetaclass):
pass
lst = CustomList()
lst.add('custom_list_1')
lst.add('custom_list_2')
lst
['custom_list_1', 'custom_list_2']
Магия вступает в силу, когда мы передаем аргументы ключевого слова в метаклассе, это означает, что интерпретатор Python создает CustomList через ListMetaclass. new (), на этом этапе мы можем, например, изменить определение класса и добавить новый метод, а затем вернуть пересмотренное определение.