Что такое метаклассы в Python и для чего мы их используем?






Обратите внимание, что этот ответ предназначен для Python 2.x, поскольку он был написан в 2008 году, метаклассы немного отличаются в 3.x.
Метаклассы - это секретный соус, который заставляет «класс» работать. Метакласс по умолчанию для нового объекта стиля называется «тип».
class type(object)
| type(object) -> the object's type
| type(name, bases, dict) -> a new type
Метаклассы принимают 3 аргумента. 'имя', 'базы' и 'диктовать'
Вот где начинается секрет. Посмотрите, откуда берутся имя, базы и словарь в этом примере определения класса.
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'
А теперь пример, который на самом деле что-то означает, это автоматически сделает переменные в списке «атрибутов» установленными для класса и установят значение None.
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
Обратите внимание, что магическое поведение, которое Initialised получает благодаря наличию метакласса init_attributes, не передается в подкласс Initialised.
Вот еще более конкретный пример, показывающий, как можно создать подкласс «тип», чтобы создать метакласс, который выполняет действие при создании класса. Это довольно сложно:
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
Я думаю, что введение 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%.
Одно из применений метаклассов - автоматическое добавление новых свойств и методов к экземпляру.
Например, если вы посмотрите на Модели Django, их определение выглядит немного запутанным. Похоже, вы определяете только свойства класса:
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
Однако во время выполнения объекты Person наполнены всевозможными полезными методами. См. источник для получения некоторой удивительной метаклассификации.
Метакласс - это класс класса. Класс определяет, как ведет себя экземпляр класса (то есть объект), в то время как метакласс определяет, как ведет себя класс. Класс - это экземпляр метакласса.
Хотя в Python вы можете использовать произвольные вызываемые объекты для метаклассов (как показывает Джеруб), лучший подход - сделать его самим фактическим классом. type - это обычный метакласс в Python. type сам по себе является классом, и это отдельный тип. Вы не сможете воссоздать что-то вроде type только на Python, но Python немного обманывает. Чтобы создать свой собственный метакласс в Python, вам действительно нужно создать подкласс type.
Метакласс чаще всего используется как фабрика классов. Когда вы создаете объект, вызывая класс, Python создает новый класс (когда он выполняет оператор class), вызывая метакласс. Таким образом, в сочетании с обычными методами __init__ и __new__, метаклассы позволяют вам делать «дополнительные вещи» при создании класса, например регистрировать новый класс в каком-либо реестре или полностью заменять класс чем-то другим.
Когда выполняется инструкция class, Python сначала выполняет тело инструкции class как обычный блок кода. Результирующее пространство имен (dict) содержит атрибуты будущего класса. Метакласс определяется путем просмотра базовых классов будущего класса (метаклассы наследуются), атрибута __metaclass__ будущего класса (если есть) или глобальной переменной __metaclass__. Затем вызывается метакласс с именем, базами и атрибутами класса для его создания.
Однако метаклассы фактически определяют тип класса, а не только его фабрику, поэтому с ними можно делать гораздо больше. Вы можете, например, определить обычные методы в метаклассе. Эти метаклассы-методы похожи на методы классов в том смысле, что их можно вызывать в классе без экземпляра, но они также не похожи на методы классов в том, что их нельзя вызывать в экземпляре класса. type.__subclasses__() - это пример метода в метаклассе type. Вы также можете определить обычные «магические» методы, такие как __add__, __iter__ и __getattr__, для реализации или изменения поведения класса.
Вот агрегированный пример кусочков и кусочков:
def make_hook(f):
"""Decorator to turn 'foo' method into '__foo__'"""
f.is_hook = 1
return f
class MyType(type):
def __new__(mcls, name, bases, attrs):
if name.startswith('None'):
return None
# Go over attributes and see if they should be renamed.
newattrs = {}
for attrname, attrvalue in attrs.iteritems():
if getattr(attrvalue, 'is_hook', 0):
newattrs['__%s__' % attrname] = attrvalue
else:
newattrs[attrname] = attrvalue
return super(MyType, mcls).__new__(mcls, name, bases, newattrs)
def __init__(self, name, bases, attrs):
super(MyType, self).__init__(name, bases, attrs)
# classregistry.register(self, self.interfaces)
print "Would register class %s now." % self
def __add__(self, other):
class AutoClass(self, other):
pass
return AutoClass
# Alternatively, to autogenerate the classname as well as the class:
# return type(self.__name__ + other.__name__, (self, other), {})
def unregister(self):
# classregistry.unregister(self)
print "Would unregister class %s now." % self
class MyObject:
__metaclass__ = MyType
class NoneSample(MyObject):
pass
# Will print "NoneType None"
print type(NoneSample), repr(NoneSample)
class Example(MyObject):
def __init__(self, value):
self.value = value
@make_hook
def add(self, other):
return self.__class__(self.value + other.value)
# Will unregister the class
Example.unregister()
inst = Example(10)
# Will fail with an AttributeError
#inst.unregister()
print inst + inst
class Sibling(MyObject):
pass
ExampleSibling = Example + Sibling
# ExampleSibling is now a subclass of both Example and Sibling (with no
# content of its own) although it will believe it's called 'AutoClass'
print ExampleSibling
print ExampleSibling.__mro__
class A(type):pass<NEWLINE>class B(type,metaclass=A):pass<NEWLINE>b.__class__ = b
ppperry он, очевидно, имел в виду, что вы не можете воссоздать тип без использования самого типа в качестве метакласса. Что достаточно справедливо, чтобы сказать.
Не следует ли вызывать unregister () экземпляром класса Example?
Обратите внимание, что __metaclass__ не поддерживается в Python 3. В Python 3 используйте class MyObject(metaclass=MyType), см. python.org/dev/peps/pep-3115 и ответ ниже.
The metaclass is determined by looking at the baseclasses of the class-to-be (metaclasses are inherited), at the __metaclass__ attribute of the class-to-be (if any) or the __metaclass__ global variable.; Это правильный заказ? Или python сначала рассмотрит __metaclass__ в будущем классе, а затем его базовые классы, а затем глобальный __metaclass__?
Какую версию Python предполагает этот пример кода и имеет ли это значение?
@ thomas-wouters Это строка metaclasses are inherited правильная? Ответ Джеруба говорит, что метаклассы не унаследованы. Кроме того, что произойдет, если класс является подклассом нескольких классов, и каждый из них имеет свой собственный __metaclass__ (или два или более родительских класса определяют __metaclass__).
В документации описывается как выбирается метакласс. Метакласс не столько наследуется, сколько является производным. Если вы указываете метакласс, он должен быть подтипом каждого метакласса базового класса; в противном случае вы будете использовать метакласс базового класса, который является подтипом каждого другого метакласса базового класса. Обратите внимание, что вполне возможно, что действительный метакласс нет может быть найден, и определение не будет выполнено.
Ну может кто-нибудь также объяснить, что делает регистровый тег? Я использую класс X (ABC): у меня много времени на создание интерфейсов, но что именно регистр позволит мне сделать? В документации буквально одна строчка говорится о регистре.
Что говорит @BlackShift. Здесь можно было бы использовать большое жирное предупреждение вверху о том, что весь ответ устарел, поскольку он полагается на Python 2, который сам по себе является устаревшим.
Другие объяснили, как работают метаклассы и как они вписываются в систему типов 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, который записывает порядок, в котором были определены классы.
Спасибо за пример. Почему вам это показалось проще, чем наследование от MyBase, __init__(self) которой соответствует type(self)._order = MyBase.counter; MyBase.counter += 1?
Я хотел, чтобы были пронумерованы сами классы, а не их экземпляры.
Хорошо, да. Спасибо. Мой код сбрасывал атрибут MyType при каждом создании экземпляра и никогда не устанавливал атрибут, если экземпляр MyType никогда не создавался. Ой. (И свойство класса также может работать, но, в отличие от метакласса, оно не предлагает очевидного места для хранения счетчика.)
Это очень интересный пример, не в последнюю очередь потому, что можно действительно понять, почему с этим может понадобиться метакласс, чтобы предоставить решение конкретной трудности. OTOH Я изо всех сил пытаюсь убедить, что кому-то действительно нужно создавать экземпляры объектов в том порядке, в котором были определены их классы: я думаю, нам просто нужно поверить вам на слово :).
Метакласс - это класс, который сообщает, как (какой-то) другой класс должен быть создан.
Это тот случай, когда я увидел в метаклассе решение моей проблемы: У меня была действительно сложная проблема, которую, вероятно, можно было решить по-другому, но я решил решить ее с помощью метакласса. Из-за сложности это один из немногих модулей, которые я написал, где комментарии в модуле превосходят объем написанного кода. Вот...
#!/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()
What are metaclasses? What do you use them for?
TL; DR: метакласс создает и определяет поведение для класса точно так же, как класс создает и определяет поведение для экземпляра.
Псевдокод:
>>> 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 выполняет его, он использует метакласс для создания экземпляра объекта класса (который, в свою очередь, будет использоваться для создания экземпляров этого класса).
Так же, как мы можем использовать определения классов для изменения поведения экземпляров настраиваемых объектов, мы можем использовать определение класса метакласса для изменения поведения объекта класса.
Для чего их можно использовать? Из документы:
The potential uses for metaclasses are boundless. Some ideas that have been explored include logging, interface checking, automatic delegation, automatic property creation, proxies, frameworks, and automatic resource locking/synchronization.
Тем не менее, пользователям обычно рекомендуется избегать использования метаклассов без крайней необходимости.
Когда вы пишете определение класса, например, вот так,
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__, то есть пространство имен:
>>> 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__ классов по умолчанию:
>>> Foo
<class '__main__.Foo'>
Одна из самых ценных вещей, которые мы можем сделать по умолчанию при написании объекта Python, - это снабдить его хорошим __repr__. Когда мы звоним help(repr), мы узнаем, что есть хороший тест для __repr__, который также требует проверки на равенство - obj == eval(repr(obj)). Следующая простая реализация __repr__ и __eq__ для экземпляров класса нашего типа class предоставляет нам демонстрацию, которая может улучшить __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')
Обратите внимание, что этот пример был адаптирован из документация - это делает новый enum в стандартной библиотеке.
Итак, мы создали экземпляр метакласса, создав класс. Мы также можем обращаться с метаклассом, как с любым другим классом. Он имеет порядок разрешения методов:
>>> 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 3
В метаклассе есть (на данный момент) два ключевых метода:
__prepare__ и__new____prepare__ позволяет вам предоставить настраиваемое сопоставление (например, OrderedDict), которое будет использоваться в качестве пространства имен при создании класса. Вы должны вернуть экземпляр любого выбранного вами пространства имен. Если вы не реализуете __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)
Простой пример:
Скажем, вы хотите, чтобы для ваших атрибутов запускался какой-то простой код проверки - как будто это всегда должен быть int или 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
Вау, это потрясающая новая функция, о которой я не знал, что она существует в Python 3. Спасибо за пример !!
Обратите внимание, что, начиная с python 3.6, вы можете использовать __set_name__(cls, name) в дескрипторе (ValidateType), чтобы установить имя в дескрипторе (self.name и в этом случае также self.attr). Это было добавлено, чтобы не погружаться в метаклассы для этого конкретного общего варианта использования (см. PEP 487).
type на самом деле является metaclass - классом, который создает другие классы.
Большинство metaclass являются подклассами type. metaclass получает класс new в качестве своего первого аргумента и предоставляет доступ к объекту класса с подробностями, как указано ниже:
>>> class MetaClass(type):
... def __init__(cls, name, bases, attrs):
... print ('class name: %s' %name )
... print ('Defining class %s' %cls)
... print('Bases %s: ' %bases)
... print('Attributes')
... for (name, value) in attrs.items():
... print ('%s :%r' %(name, value))
...
>>> class NewClass(object, metaclass=MetaClass):
... get_choch='dairy'
...
class name: NewClass
Bases <class 'object'>:
Defining class <class 'NewClass'>
get_choch :'dairy'
__module__ :'builtins'
__qualname__ :'NewClass'
Note:
Обратите внимание, что экземпляр класса не создавался ни разу; простой акт создания класса запускает выполнение metaclass.
__call__() метакласса при создании экземпляра классаЕсли вы занимаетесь программированием на Python более нескольких месяцев, вы в конечном итоге наткнетесь на код, который выглядит следующим образом:
# define a class
class SomeClass(object):
# ...
# some definition here ...
# ...
# create an instance of it
instance = SomeClass()
# then call the object as if it's a function
result = instance('foo', 'bar')
Последнее возможно, когда вы реализуете магический метод __call__() в классе.
class SomeClass(object):
# ...
# some definition here ...
# ...
def __call__(self, foo, bar):
return bar + foo
Метод __call__() вызывается, когда экземпляр класса используется как вызываемый. Но, как мы видели из предыдущих ответов, сам класс является экземпляром метакласса, поэтому, когда мы используем класс как вызываемый (т.е. когда мы создаем его экземпляр), мы фактически вызываем метод __call__() его метакласса. На этом этапе большинство программистов Python немного сбиты с толку, потому что им сказали, что при создании экземпляра, подобного этому instance = SomeClass(), вы вызываете его метод __init__(). Некоторые, кто копнул немного глубже, знают, что до __init__() был __new__(). Что ж, сегодня открывается еще один пласт истины, до __new__() был метакласс __call__().
Давайте изучим цепочку вызовов методов конкретно с точки зрения создания экземпляра класса.
Это метакласс, который регистрирует точно момент до создания экземпляра и момент, когда он собирается его вернуть.
class Meta_1(type):
def __call__(cls):
print "Meta_1.__call__() before creating an instance of ", cls
instance = super(Meta_1, cls).__call__()
print "Meta_1.__call__() about to return instance."
return instance
Это класс, который использует этот метакласс
class Class_1(object):
__metaclass__ = Meta_1
def __new__(cls):
print "Class_1.__new__() before creating an instance."
instance = super(Class_1, cls).__new__(cls)
print "Class_1.__new__() about to return instance."
return instance
def __init__(self):
print "entering Class_1.__init__() for instance initialization."
super(Class_1,self).__init__()
print "exiting Class_1.__init__()."
А теперь создадим экземпляр Class_1.
instance = Class_1()
# Meta_1.__call__() before creating an instance of <class '__main__.Class_1'>.
# Class_1.__new__() before creating an instance.
# Class_1.__new__() about to return instance.
# entering Class_1.__init__() for instance initialization.
# exiting Class_1.__init__().
# Meta_1.__call__() about to return instance.
Обратите внимание, что приведенный выше код на самом деле не делает ничего, кроме регистрации задач. Каждый метод делегирует фактическую работу своей родительской реализации, таким образом сохраняя поведение по умолчанию. Поскольку type является родительским классом Meta_1 (type является родительским метаклассом по умолчанию) и с учетом упорядоченной последовательности вывода выше, мы теперь имеем представление о том, какой будет псевдореализация type.__call__():
class type:
def __call__(cls, *args, **kwarg):
# ... maybe a few things done to cls here
# then we call __new__() on the class to create an instance
instance = cls.__new__(cls, *args, **kwargs)
# ... maybe a few things done to the instance here
# then we initialize the instance with its __init__() method
instance.__init__(*args, **kwargs)
# ... maybe a few more things done to instance here
# then we return it
return instance
Мы видим, что метод __call__() метакласса вызывается первым. Затем он делегирует создание экземпляра методу __new__() класса и инициализацию __init__() экземпляра. Это также тот, который в конечном итоге возвращает экземпляр.
Из вышесказанного следует, что метаклассу __call__() также предоставляется возможность решить, будет ли в конечном итоге сделан вызов Class_1.__new__() или Class_1.__init__(). В ходе выполнения он может фактически вернуть объект, который не был затронут ни одним из этих методов. Возьмем, например, такой подход к шаблону singleton:
class Meta_2(type):
singletons = {}
def __call__(cls, *args, **kwargs):
if cls in Meta_2.singletons:
# we return the only instance and skip a call to __new__()
# and __init__()
print ("{} singleton returning from Meta_2.__call__(), "
"skipping creation of new instance.".format(cls))
return Meta_2.singletons[cls]
# else if the singleton isn't present we proceed as usual
print "Meta_2.__call__() before creating an instance."
instance = super(Meta_2, cls).__call__(*args, **kwargs)
Meta_2.singletons[cls] = instance
print "Meta_2.__call__() returning new instance."
return instance
class Class_2(object):
__metaclass__ = Meta_2
def __new__(cls, *args, **kwargs):
print "Class_2.__new__() before creating instance."
instance = super(Class_2, cls).__new__(cls)
print "Class_2.__new__() returning instance."
return instance
def __init__(self, *args, **kwargs):
print "entering Class_2.__init__() for initialization."
super(Class_2, self).__init__()
print "exiting Class_2.__init__()."
Давайте посмотрим, что происходит при многократных попытках создать объект типа Class_2.
a = Class_2()
# Meta_2.__call__() before creating an instance.
# Class_2.__new__() before creating instance.
# Class_2.__new__() returning instance.
# entering Class_2.__init__() for initialization.
# exiting Class_2.__init__().
# Meta_2.__call__() returning new instance.
b = Class_2()
# <class '__main__.Class_2'> singleton returning from Meta_2.__call__(), skipping creation of new instance.
c = Class_2()
# <class '__main__.Class_2'> singleton returning from Meta_2.__call__(), skipping creation of new instance.
a is b is C# True
Это хорошее дополнение к ранее одобренному «принятому ответу». Он предоставляет примеры для программистов среднего уровня.
Функция type(obj) определяет тип объекта.
type() класса - это его метакласс.
Чтобы использовать метакласс:
class Foo(object):
__metaclass__ = MyMetaClass
type - это собственный метакласс. Класс класса - это метакласс - тело класса - это аргументы, переданные метаклассу, который используется для создания класса.
Здесь вы можете прочитать о том, как использовать метаклассы для настройки конструкции классов.
Классы Python сами по себе являются объектами - как, например, - своего метакласса.
Метакласс по умолчанию, который применяется, когда вы определяете классы как:
class foo:
...
мета-класс используются для применения некоторого правила ко всему набору классов. Например, предположим, что вы создаете ORM для доступа к базе данных и хотите, чтобы записи из каждой таблицы относились к классу, сопоставленному с этой таблицей (на основе полей, бизнес-правил и т. д.), Возможное использование метакласса это, например, логика пула соединений, которая используется всеми классами записей из всех таблиц. Другое использование - логика для поддержки внешних ключей, которая включает несколько классов записей.
когда вы определяете метакласс, вы создаете подкласс и можете переопределить следующие магические методы для вставки своей логики.
class somemeta(type):
__new__(mcs, name, bases, clsdict):
"""
mcs: is the base metaclass, in this case type.
name: name of the new class, as provided by the user.
bases: tuple of base classes
clsdict: a dictionary containing all methods and attributes defined on class
you must return a class object by invoking the __new__ constructor on the base metaclass.
ie:
return type.__call__(mcs, name, bases, clsdict).
in the following case:
class foo(baseclass):
__metaclass__ = somemeta
an_attr = 12
def bar(self):
...
@classmethod
def foo(cls):
...
arguments would be : ( somemeta, "foo", (baseclass, baseofbase,..., object), {"an_attr":12, "bar": <function>, "foo": <bound class method>}
you can modify any of these values before passing on to type
"""
return type.__call__(mcs, name, bases, clsdict)
def __init__(self, name, bases, clsdict):
"""
called after type has been created. unlike in standard classes, __init__ method cannot modify the instance (cls) - and should be used for class validaton.
"""
pass
def __prepare__():
"""
returns a dict or something that can be used as a namespace.
the type will then attach methods and attributes from class definition to it.
call order :
somemeta.__new__ -> type.__new__ -> type.__init__ -> somemeta.__init__
"""
return dict()
def mymethod(cls):
""" works like a classmethod, but for class objects. Also, my method will not be visible to instances of cls.
"""
pass
в любом случае, эти два крючка используются чаще всего. Метаклассирование является мощным средством, и выше далеко не исчерпывающий список применений метаклассирования.
Функция 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
Помимо использования type () для динамического создания классов, вы можете управлять поведением создания класса и использовать метакласс.
Согласно объектной модели Python, класс является объектом, поэтому класс должен быть экземпляром другого определенного класса. По умолчанию класс Python является экземпляром класса типа. То есть тип - это метакласс большинства встроенных классов и метакласс определяемых пользователем классов.
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. новый (), на этом этапе мы можем изменить определение класса, например, и добавить новый метод, а затем вернуть исправленное определение.
В дополнение к опубликованным ответам я могу сказать, что metaclass определяет поведение класса. Итак, вы можете явно установить свой метакласс. Когда Python получает ключевое слово class, он начинает поиск metaclass. Если он не найден - для создания объекта класса используется тип метакласса по умолчанию. Используя атрибут __metaclass__, вы можете установить metaclass своего класса:
class MyClass:
__metaclass__ = type
# write here other method
# write here one more method
print(MyClass.__metaclass__)
Результат будет примерно таким:
class 'type'
И, конечно же, вы можете создать свой собственный metaclass для определения поведения любого класса, созданного с использованием вашего класса.
Для этого ваш класс типа metaclass по умолчанию должен быть унаследован, поскольку это основной metaclass:
class MyMetaClass(type):
__metaclass__ = type
# you can write here any behaviour you want
class MyTestClass:
__metaclass__ = MyMetaClass
Obj = MyTestClass()
print(Obj.__metaclass__)
print(MyMetaClass.__metaclass__)
Результат будет:
class '__main__.MyMetaClass'
class 'type'
В объектно-ориентированном программировании метакласс - это класс, экземпляры которого являются классами. Так же, как обычный класс определяет поведение определенных объектов, метакласс определяет поведение определенного класса и их экземпляров. Термин метакласс просто означает что-то, что используется для создания классов. Другими словами, это класс класса. Метакласс используется для создания класса, поэтому, как объект, являющийся экземпляром класса, класс является экземпляром метакласса. В Python классы также считаются объектами.
Вместо того, чтобы давать книжные определения, было бы лучше, если бы вы добавили несколько примеров. Первая строка вашего ответа, похоже, была скопирована из записи метаклассов в Википедии.
@verisimilitude Я тоже изучаю, можете ли вы помочь мне улучшить этот ответ, предоставив несколько практических примеров из своего опыта ??
«В объектно-ориентированном программировании метакласс - это класс, экземпляры которого являются классами. Так же, как обычный класс определяет поведение определенных объектов, метакласс определяет поведение определенных классов и их экземпляров». Метакласс - Википедия. Первые 2 предложения, дословно. Какую пользу этот ответ принесет кому-либо, кроме вашей репутации? Ваша специальность - не Python, метаклассы - одни из самых сложных и наименее понятных концепций Python, и здесь нет недостатка в отличных ответах. Что дает копипаст?
Вот еще один пример того, для чего его можно использовать:
metaclass для изменения функции его экземпляра (класса).class MetaMemberControl(type):
__slots__ = ()
@classmethod
def __prepare__(mcs, f_cls_name, f_cls_parents, # f_cls means: future class
meta_args=None, meta_options=None): # meta_args and meta_options is not necessarily needed, just so you know.
f_cls_attr = dict()
if not "do something or if you want to define your cool stuff of dict...":
return dict(make_your_special_dict=None)
else:
return f_cls_attr
def __new__(mcs, f_cls_name, f_cls_parents, f_cls_attr,
meta_args=None, meta_options=None):
original_getattr = f_cls_attr.get('__getattribute__')
original_setattr = f_cls_attr.get('__setattr__')
def init_getattr(self, item):
if not item.startswith('_'): # you can set break points at here
alias_name = '_' + item
if alias_name in f_cls_attr['__slots__']:
item = alias_name
if original_getattr is not None:
return original_getattr(self, item)
else:
return super(eval(f_cls_name), self).__getattribute__(item)
def init_setattr(self, key, value):
if not key.startswith('_') and ('_' + key) in f_cls_attr['__slots__']:
raise AttributeError(f"you can't modify private members:_{key}")
if original_setattr is not None:
original_setattr(self, key, value)
else:
super(eval(f_cls_name), self).__setattr__(key, value)
f_cls_attr['__getattribute__'] = init_getattr
f_cls_attr['__setattr__'] = init_setattr
cls = super().__new__(mcs, f_cls_name, f_cls_parents, f_cls_attr)
return cls
class Human(metaclass=MetaMemberControl):
__slots__ = ('_age', '_name')
def __init__(self, name, age):
self._name = name
self._age = age
def __getattribute__(self, item):
"""
is just for IDE recognize.
"""
return super().__getattribute__(item)
""" with MetaMemberControl then you don't have to write as following
@property
def name(self):
return self._name
@property
def age(self):
return self._age
"""
def test_demo():
human = Human('Carson', 27)
# human.age = 18 # you can't modify private members:_age <-- this is defined by yourself.
# human.k = 18 # 'Human' object has no attribute 'k' <-- system error.
age1 = human._age # It's OK, although the IDE will show some warnings. (Access to a protected member _age of a class)
age2 = human.age # It's OK! see below:
"""
if you do not define `__getattribute__` at the class of Human,
the IDE will show you: Unresolved attribute reference 'age' for class 'Human'
but it's ok on running since the MetaMemberControl will help you.
"""
if __name__ == '__main__':
test_demo()
metaclass мощный, с его помощью можно делать много вещей (например, обезьянья магия), но будьте осторожны, это может быть известно только вам.
Класс в Python - это объект, и, как и любой другой объект, он является экземпляром «чего-то». Это «что-то» называется метаклассом. Этот метакласс - это особый тип класса, который создает объекты других классов. Следовательно, метакласс отвечает за создание новых классов. Это позволяет программисту настраивать способ создания классов.
Для создания метакласса обычно выполняется переопределение методов новый () и в этом (). новый () можно переопределить, чтобы изменить способ создания объектов, а в этом () можно переопределить, чтобы изменить способ инициализации объекта. Метакласс можно создать несколькими способами. Один из способов - использовать функцию type (). Функция type () при вызове с 3 параметрами создает метакласс. Параметры следующие: -
Другой способ создания метакласса состоит из ключевого слова «метакласс». Определите метакласс как простой класс. В параметрах унаследованного класса передайте metaclass = metaclass_name
Metaclass можно специально использовать в следующих ситуациях: -
Обратите внимание, что в python 3.6 был введен новый dunder-метод __init_subclass__(cls, **kwargs), который заменяет множество распространенных вариантов использования метаклассов. Is вызывается при создании подкласса определяющего класса. См. документы python.
Метакласс - это своего рода класс, который определяет, как класс будет вести себя, или мы можем сказать, что класс сам по себе является экземпляром метакласса.
Пожалуйста, добавьте релевантную информацию .. ваши комментарии сбивают с толку
Разве использование мета-классов не добавляет новые свойства и методы к учебный класс, а не к экземпляру? Насколько я понял, мета-класс изменяет сам класс, и в результате экземпляры могут быть созданы по-разному измененным классом. Может немного ввести в заблуждение людей, которые пытаются понять природу мета-класса. Наличие полезных методов в экземплярах может быть достигнуто путем обычного наследования. Хотя ссылка на код Django в качестве примера хороша.