В Python, что такое метаклассы и для чего мы их используем?
В Python, что такое метаклассы и для чего мы их используем?
Ответы:
Метакласс - это класс класса. Класс определяет, как ведет себя экземпляр класса (то есть объект), а метакласс определяет поведение класса. Класс является экземпляром метакласса.
В то время как в Python вы можете использовать произвольные вызываемые элементы для метаклассов (как показано на Jerub ), лучший подход состоит в том, чтобы сделать его самим классом. 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__
__metaclass__
не поддерживается в Python 3. В Python 3 используйте class MyObject(metaclass=MyType)
, см. Python.org/dev/peps/pep-3115 и ответ ниже.
Прежде чем разбираться в метаклассах, вам нужно освоить классы на Python. И у Python очень своеобразное представление о том, что такое классы, заимствованные из языка Smalltalk.
В большинстве языков классы - это просто фрагменты кода, которые описывают, как создать объект. Это также верно в Python:
>>> class ObjectCreator(object):
... pass
...
>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>
Но классы больше, чем в Python. Классы тоже объекты.
Да, объекты.
Как только вы используете ключевое слово class
, Python выполняет его и создает ОБЪЕКТ. Инструкция
>>> 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, bases, attrs)
Куда:
name
: название классаbases
: кортеж родительского класса (для наследования может быть пустым)attrs
: словарь, содержащий имена и значения атрибутовнапример:
>>> 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. Это включает в себя целые, строки, функции и классы. Все они объекты. И все они были созданы из класса:
>>> 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 делает следующее:
Есть ли __metaclass__
атрибут в Foo
?
Если да, создайте в памяти объект класса (я сказал объект класса, оставайтесь со мной здесь), с именем Foo
, используя то, что в __metaclass__
.
Если Python не может найти __metaclass__
, он будет искать __metaclass__
на уровне MODULE и попытаться сделать то же самое (но только для классов, которые ничего не наследуют, в основном классы старого стиля).
Затем, если он вообще не может его найти __metaclass__
, он будет использовать Bar
собственный метакласс (первого родителя) (который может быть по умолчанию type
) для создания объекта класса.
Будьте осторожны, что __metaclass__
атрибут не будет наследоваться, метакласс parent ( Bar.__class__
) будет. Если Bar
используется __metaclass__
атрибут, созданный Bar
с type()
(и не type.__new__()
), подклассы не будут наследовать это поведение.
Теперь большой вопрос, что вы можете вставить __metaclass__
?
Ответ: что-то, что может создать класс.
А что может создать класс? type
или все, что подклассы или использует его.
Синтаксис для установки метакласса был изменен в Python 3:
class Foo(object, metaclass=something):
...
т.е. __metaclass__
атрибут больше не используется, в пользу ключевого аргумента в списке базовых классов.
Поведение метаклассов, однако, остается в основном таким же .
Одна вещь, добавленная к метаклассам в Python 3, заключается в том, что вы также можете передавать атрибуты как аргументы-ключевые слова в метакласс, например, так:
class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2):
...
Прочитайте раздел ниже, чтобы узнать, как Python справляется с этим.
Основное назначение метакласса - автоматическое изменение класса при его создании.
Вы обычно делаете это для API, где вы хотите создать классы, соответствующие текущему контексту.
Представьте себе глупый пример, когда вы решаете, что все классы в вашем модуле должны иметь свои атрибуты, написанные в верхнем регистре. Есть несколько способов сделать это, но один из них - установить __metaclass__
на уровне модуля.
Таким образом, все классы этого модуля будут созданы с использованием этого метакласса, и нам просто нужно указать метаклассу, чтобы все атрибуты были заглавными.
К счастью, на __metaclass__
самом деле это может быть любой вызываемый объект, он не должен быть формальным классом (я знаю, что-то с именем «class» в названии не обязательно должно быть классом, поймите, что ... но это полезно).
Итак, начнем с простого примера с использования функции.
# 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_attrs):
"""
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_attrs = {
attr if attr.startswith("__") else attr.upper(): v
for attr, v in future_class_attrs.items()
}
# let `type` do the class creation
return type(future_class_name, future_class_parents, uppercase_attrs)
__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'
Давайте проверим:
>>> hasattr(Foo, 'bar')
False
>>> hasattr(Foo, 'BAR')
True
>>> Foo.BAR
'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_attrs):
uppercase_attrs = {
attr if attr.startswith("__") else attr.upper(): v
for attr, v in future_class_attrs.items()
}
return type(future_class_name, future_class_parents, uppercase_attrs)
Давайте перепишем вышесказанное, но теперь с более короткими и реалистичными именами переменных, теперь мы знаем, что они означают:
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, attrs):
uppercase_attrs = {
attr if attr.startswith("__") else attr.upper(): v
for attr, v in attrs.items()
}
return type(clsname, bases, uppercase_attrs)
Возможно, вы заметили дополнительный аргумент cls
. В этом нет ничего особенного: __new__
всегда получает класс, в котором он определен, в качестве первого параметра. Точно так же, как у self
обычных методов, которые получают экземпляр в качестве первого параметра или определяющий класс для методов класса.
Но это не правильный ООП. Мы звоним type
напрямую, и мы не переопределяем или не звоним родителям __new__
. Давайте сделаем это вместо этого:
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, attrs):
uppercase_attrs = {
attr if attr.startswith("__") else attr.upper(): v
for attr, v in attrs.items()
}
return type.__new__(cls, clsname, bases, uppercase_attrs)
Мы можем сделать его еще чище, используя super
, что облегчит наследование (потому что да, у вас могут быть метаклассы, наследование от метаклассов, наследование от типа):
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, attrs):
uppercase_attrs = {
attr if attr.startswith("__") else attr.upper(): v
for attr, v in attrs.items()
}
return super(UpperAttrMetaclass, cls).__new__(
cls, clsname, bases, uppercase_attrs)
Да, и в Python 3, если вы делаете этот вызов с аргументами ключевых слов, например:
class Foo(object, metaclass=MyMetaclass, kwarg1=value1):
...
Это переводит это в метакласс, чтобы использовать это:
class MyMetaclass(type):
def __new__(cls, clsname, bases, dct, kwargs1=default):
...
Вот и все. В метаклассах больше ничего нет.
Причиной сложности кода с использованием метаклассов является не метаклассы, а то, что вы обычно используете метаклассы для скрученных вещей, полагаясь на интроспекцию, манипулирование наследованием, такие переменные __dict__
и т. Д.
Действительно, метаклассы особенно полезны для создания чёрной магии и, следовательно, сложных вещей. Но сами по себе они просты
Так как __metaclass__
может принять любой вызываемый объект, зачем вам использовать класс, поскольку он явно более сложный?
Для этого есть несколько причин:
UpperAttrMetaclass(type)
, вы знаете, что последует__new__
, __init__
и __call__
. Что позволит вам делать разные вещи. Даже если обычно вы можете делать все это __new__
, некоторым людям просто удобнее пользоваться __init__
.Теперь большой вопрос. Зачем вам использовать какую-то неясную функцию, склонную к ошибкам?
Ну, обычно вы этого не делаете:
Метаклассы - это более глубокое волшебство, о котором 99% пользователей никогда не должны беспокоиться. Если вам интересно, нужны ли они вам, вам это не нужно (люди, которые действительно нуждаются в них, точно знают, что они им нужны, и не нуждаются в объяснении того, почему).
Питон Гуру Тим Питерс
Основным вариантом использования метакласса является создание API. Типичным примером этого является Django ORM. Это позволяет вам определить что-то вроде этого:
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()
Но если вы сделаете это:
person = Person(name='bob', age='35')
print(person.age)
Это не вернет IntegerField
объект. Он вернет int
, и может даже взять его непосредственно из базы данных.
Это возможно, потому что models.Model
определяет __metaclass__
и использует некоторую магию, которая превратит Person
только что заданные вами простые выражения в сложный крюк для поля базы данных.
Django делает что-то сложное простым, предоставляя простой API и используя метаклассы, воссоздавая код из этого API для реальной работы за кулисами.
Во-первых, вы знаете, что классы - это объекты, которые могут создавать экземпляры.
Ну, на самом деле, классы сами по себе являются примерами. Метаклассов.
>>> class Foo(object): pass
>>> id(Foo)
142630324
В Python все является объектом, и все они являются экземплярами классов или экземплярами метаклассов.
За исключением type
.
type
на самом деле его собственный метакласс. Это не то, что вы могли бы воспроизвести на чистом Python, и это делается путем обмана на уровне реализации.
Во-вторых, метаклассы сложны. Возможно, вы не захотите использовать их для очень простых изменений класса. Вы можете изменить классы, используя два разных метода:
В 99% случаев вам нужно изменить класс, лучше использовать их.
Но в 98% случаев вам вообще не нужно менять класс.
models.Model
он не использует, __metaclass__
а скорее class Model(metaclass=ModelBase):
ссылается на ModelBase
класс, который затем использует магию вышеупомянутого метакласса. Отличный пост! Вот источник Django: github.com/django/django/blob/master/django/db/models/…
__metaclass__
атрибут не был унаследован, метакласс родительского ( Bar.__class__
) будет. Если Bar
использовал__metaclass__
атрибут, созданный Bar
с type()
(и не type.__new__()
), подклассы не будут наследовать такое поведение. >> - Не могли бы вы / кто-нибудь объяснить, пожалуйста, немного глубже этот отрывок?
Now you wonder why the heck is it written in lowercase, and not Type?
- хорошо, потому что это реализовано в C - это та же самая причина, по которой defaultdict является строчными, в то время как OrderedDict (в python 2) является обычным CamelCase
Обратите внимание, что этот ответ для 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
Давайте определим метакласс, который продемонстрирует, как class: называет его.
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
.
Вот еще более конкретный пример, показывающий, как вы можете создать подкласс 'type' для создания метакласса, который выполняет действие при создании класса. Это довольно сложно:
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. Вот пример того, для чего они могут быть использованы. В рамках тестирования, который я написал, я хотел отслеживать порядок, в котором были определены классы, чтобы впоследствии я мог их создать в этом порядке. Я нашел, что проще всего сделать это с помощью метакласса.
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
который записывает порядок, в котором классы были определены.
__init__(self)
говорит type(self)._order = MyBase.counter; MyBase.counter += 1
?
Одно из применений метаклассов - автоматическое добавление новых свойств и методов в экземпляр.
Например, если вы посмотрите на модели Django , их определение выглядит немного запутанным. Похоже, вы определяете только свойства класса:
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
Однако во время выполнения объекты Person заполняются всевозможными полезными методами. Посмотрите источник для некоторой удивительной метаклассерии.
Я думаю, что введение ONLamp в программирование метаклассов хорошо написано и дает действительно хорошее введение в тему, хотя ему уже несколько лет.
http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html (архивируется по адресу https://web.archive.org/web/20080206005253/http://www.onlamp. ком / паб / а / питон / 2003/04/17 / metaclasses.html )
Вкратце: класс - это план для создания экземпляра, метакласс - это план для создания класса. Легко видеть, что в классах Python должны быть объекты первого класса, чтобы включить это поведение.
Сам я никогда не писал, но я думаю, что одно из самых хороших применений метаклассов можно увидеть в структуре Django . Классы моделей используют метаклассовый подход, чтобы включить декларативный стиль написания новых моделей или классов форм. Пока метакласс создает класс, все члены получают возможность настраивать сам класс.
Осталось сказать следующее: если вы не знаете, что такое метаклассы, вероятность того, что они вам не понадобятся, составляет 99%.
Что такое метаклассы? Для чего вы их используете?
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 выполняет его, он использует метакласс для создания экземпляра объекта класса (который, в свою очередь, будет использоваться для создания экземпляров этого класса).
Так же, как мы можем использовать определения классов, чтобы изменить поведение пользовательских объектов, мы можем использовать определение класса метакласса, чтобы изменить поведение объекта класса.
Для чего они могут быть использованы? Из документов :
Потенциальное использование метаклассов безгранично. Некоторые идеи, которые были изучены, включают ведение журнала, проверку интерфейса, автоматическое делегирование, автоматическое создание свойств, прокси, платформы и автоматическую блокировку / синхронизацию ресурсов.
Тем не менее, пользователям обычно рекомендуется избегать использования метаклассов, если в этом нет крайней необходимости.
Когда вы пишете определение класса, например, как это,
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__
для экземпляров класса нашего класса типов предоставляет нам демонстрацию, которая может улучшить стандартные __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
__set_name__(cls, name)
в дескрипторе ( ValidateType
), чтобы установить имя в дескрипторе ( self.name
и в этом случае также self.attr
). Это было добавлено, чтобы не приходилось погружаться в метаклассы для этого конкретного общего случая использования (см. PEP 487).
__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__()
конечном итоге будет. За время своего выполнения он мог фактически вернуть объект, который не был затронут ни одним из этих методов. Возьмем для примера такой подход к шаблону синглтона:
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
Метакласс - это класс, который сообщает, как (какой-то) другой класс должен быть создан.
Это тот случай, когда я рассматривал метакласс как решение своей проблемы: у меня была действительно сложная проблема, которая, возможно, могла быть решена по-другому, но я решил решить ее с помощью метакласса. Из-за сложности, это один из немногих написанных мной модулей, где комментарии в модуле превосходят объем написанного кода. Вот...
#!/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(obj)
Функция получает вас тип объекта.
type()
Класса является его метаклассом .
Чтобы использовать метакласс:
class Foo(object):
__metaclass__ = MyMetaClass
type
это свой собственный метакласс. Класс класса является метаклассом - тело класса - это аргументы, передаваемые метаклассу, который используется для создания класса.
Здесь вы можете прочитать о том, как использовать метаклассы для настройки построения классов.
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
.
Классы 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 является экземпляром класса 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 (), на этом этапе мы можем изменить определение класса, например, добавить новый метод и затем вернуть исправленное определение.
В дополнение к опубликованным ответам я могу сказать, что a 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 классы также считаются объектами.
Вот еще один пример того, для чего он может быть использован:
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 - это объект, и, как и любой другой объект, это экземпляр «чего-то». Это «что-то» - это то, что называется метаклассом. Этот метакласс является особым типом класса, который создает объекты другого класса. Следовательно, метакласс отвечает за создание новых классов. Это позволяет программисту настроить способ генерации классов.
Для создания метакласса обычно выполняется переопределение методов new () и init (). new () может быть переопределен, чтобы изменить способ создания объектов, в то время как init () может быть переопределен, чтобы изменить способ инициализации объекта. Метакласс может быть создан несколькими способами. Одним из способов является использование функции type (). Функция type () при вызове с 3 параметрами создает метакласс. Параметры: -
Другой способ создания метакласса состоит из ключевого слова metaclass. Определите метакласс как простой класс. В параметрах унаследованного класса передайте metaclass = metaclass_name
Метакласс может быть специально использован в следующих ситуациях:
Обратите внимание, что в Python 3.6 появился новый метод dunder, __init_subclass__(cls, **kwargs)
который заменил множество распространенных вариантов использования метаклассов. Вызывается, когда создается подкласс определяющего класса. Смотрите документы по питону .
Метакласс - это своего рода класс, который определяет, как класс будет себя вести, или мы можем сказать, что класс сам является экземпляром метакласса.
class A(type):pass<NEWLINE>class B(type,metaclass=A):pass<NEWLINE>b.__class__ = b