Классы как объекты
Прежде чем разбираться в метаклассах, вам нужно освоить классы на 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 of the class,
tuple of the parent class (for inheritance, can be empty),
dictionary containing attributes names and values)
например:.
>>> class MyShinyClass(object):
... pass
можно создать вручную следующим образом:
>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # create an instance with the class
<__main__.MyShinyClass object at 0x8997cec>
Вы заметите, что мы используем «MyShinyClass» в качестве имени класса
и как переменная для хранения ссылки на класс. Они могут быть разными,
но нет никаких причин, чтобы усложнять вещи.
type
принимает словарь для определения атрибутов класса. Итак:
>>> class Foo(object):
... bar = True
Можно перевести на:
>>> Foo = type('Foo', (), {'bar':True})
И используется как обычный класс:
>>> print(Foo)
<class '__main__.Foo'>
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar)
True
И, конечно, вы можете наследовать от него, так:
>>> class FooChild(Foo):
... pass
будет:
>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # bar is inherited from Foo
True
В конце концов, вы захотите добавить методы в ваш класс. Просто определите функцию
с правильной подписью и назначьте его в качестве атрибута.
>>> def echo_bar(self):
... print(self.bar)
...
>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
>>> hasattr(Foo, 'echo_bar')
False
>>> hasattr(FooChild, 'echo_bar')
True
>>> my_foo = FooChild()
>>> my_foo.echo_bar()
True
И вы можете добавить еще больше методов после динамического создания класса, точно так же, как добавление методов в нормально созданный объект класса.
>>> def echo_bar_more(self):
... print('yet another method')
...
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
True
Вы видите, куда мы идем: в Python классы являются объектами, и вы можете динамически создавать класс на лету.
Это то, что делает Python, когда вы используете ключевое слово class
, и делает это, используя метакласс.
Что такое метаклассы (наконец)
Метаклассы - это «материал», который создает классы.
Вы определяете классы для создания объектов, верно?
Но мы узнали, что классы Python являются объектами.
Ну, именно метаклассы создают эти объекты. Они классы классов,
Вы можете изобразить их так:
MyClass = MetaClass()
my_object = MyClass()
Вы видели, что type
позволяет вам делать что-то вроде этого:
MyClass = type('MyClass', (), {})
Это потому, что функция type
на самом деле является метаклассом. type
это
метакласс Python использует для создания всех классов за кулисами.
Теперь вы удивляетесь, почему, черт возьми, оно написано строчными буквами, а не Type
?
Ну, я думаю, это вопрос согласованности с str
, классом, который создает
строковые объекты и int
класс, который создает целочисленные объекты. type
есть
просто класс, который создает объекты класса.
Это можно увидеть, проверив атрибут __class__
.
Все, и я имею в виду все, является объектом в Python. Это включает в себя целые,
строки, функции и классы. Все они объекты. И все они имеют
был создан из класса:
>>> 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, но, конечно, вы можете создать свой
собственный метакласс.
В 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__
не будет унаследован, метакласс родительского (Bar.__class__
) будет. Если Bar
использовал атрибут __metaclass__
, который создал Bar
с type()
(а не type.__new__()
), подклассы не будут наследовать это поведение.
Теперь большой вопрос, что вы можете вставить в __metaclass__
?
Ответ: что-то, что может создать класс.
А что может создать класс? type
, или что-либо, что подклассов или использует его.
Метаклассы в Python 3
Синтаксис для установки метакласса был изменен в 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_attr):
"""
Return a class object, with the list of its attribute turned
into uppercase.
"""
# pick up any attribute that doesn't start with '__' and uppercase it
uppercase_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
# let `type` do the class creation
return type(future_class_name, future_class_parents, uppercase_attr)
__metaclass__ = upper_attr # this will affect all classes in the module
class Foo(): # global __metaclass__ won't work with "object" though
# but we can define __metaclass__ here instead to affect only this class
# and this will work with "object" children
bar = 'bip'
print(hasattr(Foo, 'bar'))
# Out: False
print(hasattr(Foo, 'BAR'))
# Out: True
f = Foo()
print(f.BAR)
# Out: 'bip'
Теперь давайте сделаем точно так же, но используя реальный класс для метакласса:
# remember that `type` is actually a class like `str` and `int`
# so you can inherit from it
class UpperAttrMetaclass(type):
# __new__ is the method called before __init__
# it's the method that creates the object and returns it
# while __init__ just initializes the object passed as parameter
# you rarely use __new__, except when you want to control how the object
# is created.
# here the created object is the class, and we want to customize it
# so we override __new__
# you can do some stuff in __init__ too if you wish
# some advanced use involves overriding __call__ as well, but we won't
# see this
def __new__(upperattr_metaclass, future_class_name,
future_class_parents, future_class_attr):
uppercase_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return type(future_class_name, future_class_parents, uppercase_attr)
Но это не совсем ООП. Мы звоним type
напрямую и не отменяем
или позвоните родителю __new__
. Давайте сделаем это:
class UpperAttrMetaclass(type):
def __new__(upperattr_metaclass, future_class_name,
future_class_parents, future_class_attr):
uppercase_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
# reuse the type.__new__ method
# this is basic OOP, nothing magic in there
return type.__new__(upperattr_metaclass, future_class_name,
future_class_parents, uppercase_attr)
Возможно, вы заметили дополнительный аргумент upperattr_metaclass
. Есть
ничего особенного: __new__
всегда получает класс, в котором он определен, в качестве первого параметра. Также как у вас есть self
для обычных методов, которые получают экземпляр в качестве первого параметра, или определяющий класс для методов класса.
Конечно, имена, которые я здесь использовал, для ясности длинные, но вроде
для self
все аргументы имеют условные имена. Итак, настоящее производство
метакласс выглядел бы так:
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, dct):
uppercase_attr = {}
for name, val in dct.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return type.__new__(cls, clsname, bases, uppercase_attr)
Мы можем сделать его еще чище, используя super
, что облегчит наследование (потому что да, вы можете иметь метаклассы, наследуя от метаклассов, наследуя от типа):
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, dct):
uppercase_attr = {}
for name, val in dct.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)
Да, и в Python 3, если вы делаете этот вызов с аргументами ключевых слов, например:
class Foo(object, metaclass=Thing, kwarg1=value1):
...
В метаклассе это переводится как:
class Thing(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()
Но если вы сделаете это:
guy = Person(name='bob', age='35')
print(guy.age)
Не вернет IntegerField
объект. Он вернет int
и может даже взять его непосредственно из базы данных.
Это возможно, потому что models.Model
определяет __metaclass__
и
он использует магию, которая превратит Person
, который вы только что определили, с помощью простых утверждений
в сложный крюк к полю базы данных.
Django делает что-то сложное простым, предоставляя простой APIи используя метаклассы, воссоздавая код из этого API для выполнения реальной работы
за кадром.
Последнее слово
Во-первых, вы знаете, что классы - это объекты, которые могут создавать экземпляры.
На самом деле классы сами по себе являются экземплярами. Метаклассов.
>>> class Foo(object): pass
>>> id(Foo)
142630324
В Python все является объектом, и все они являются экземплярами классов
или экземпляры метаклассов.
За исключением type
.
type
на самом деле является собственным метаклассом. Это не то, что вы могли
воспроизводить на чистом Python, и немного обмануть при реализации
уровень.
Во-вторых, метаклассы сложны. Вы не можете использовать их для
очень простые изменения класса. Вы можете изменить классы, используя два различных метода:
99% времени, когда вам нужно изменить класс, вам лучше их использовать.
Но в 98% случаев вам вообще не нужно изменять класс.