Что такое метаклассы в Python?

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

4350
задан e-satis 19 сент. '08 в 9:10
источник поделиться
17 ответов

Метакласс является классом класса. Подобно классу, который определяет поведение экземпляра класса, метакласс определяет, как ведет себя класс. Класс является экземпляром метакласса.

метаклассическая диаграмма

В Python вы можете использовать произвольные вызовы для метаклассов (например, Jerub), более полезный подход состоит в том, чтобы сделать его фактически самим классом. type - обычный метакласс в Python. В случае, если вам интересно, да, type сам по себе является классом, и это его собственный тип. Вы не сможете воссоздать что-то вроде type чисто в Python, но Python немного обманывает. Чтобы создать свой собственный метакласс в Python, вы действительно просто хотите подклассом type.

Метакласс чаще всего используется как фабрика классов. Как и вы создаете экземпляр класса, вызвав класс, Python создает новый класс (когда он выполняет оператор "class" ), вызывая метакласс. В сочетании с обычными методами __ init __ и __ new __ метаклассы позволяют вам делать "лишние вещи" при создании класса, например, регистрировать новый класс с помощью какого-либо реестра или даже заменять класс с чем-то совсем другим.

Когда выполняется оператор class, Python сначала выполняет тело инструкции class как обычный блок кода. Полученное пространство имен (a dict) содержит атрибуты будущего класса. Метакласс определяется путем определения базовых классов класса-класса (метаклассы наследуются), в атрибуте __ metaclass __ для класса (если есть) или __ metaclass__. Затем метакласс вызывается с именем, базой и атрибутами класса для его создания.

Однако метаклассы фактически определяют тип для класса, а не только factory для него, поэтому вы можете сделать с ними гораздо больше. Вы можете, например, определить нормальные методы для метакласса. Эти метаклассы-методы похожи на методы класса, поскольку они могут быть вызваны в классе без экземпляра, но они также не похожи на методы класса, поскольку они не могут быть вызваны в экземпляр класса. type.__ подклассы __() - это пример метода в метаклассе type. Вы также можете определить обычные "магические" методы, такие как __ add __, __ iter __ и __ getattr __, чтобы реализовать или изменить поведение класса.

Вот агрегированный пример бит и кусков:

  def make_hook (f):    "Decorator, чтобы включить метод foo в '__foo__' "" "   f.is_hook = 1   return f

class MyType (type):   def __new __ (mcls, name, base, attrs):
       если name.startswith( "Нет" ):           return None
       # Перейдите по атрибутам и посмотрите, следует ли их переименовать.       newattrs = {}       для attrname, attrvalue в attrs.iteritems():           если getattr (attrvalue, 'is_hook', 0):               newattrs ['__% s__'% attrname] = attrvalue           еще:               newattrs [attrname] = attrvalue
       return super (MyType, mcls).__ new __ (mcls, name, bas, newattrs)
   def __init __ (self, name, base, attrs):       super (MyType, self).__ init __ (имя, базы, attrs)
       # classregistry.register(self, self.interfaces)       print "Будет зарегистрирован класс% s сейчас". % self
   def __add __ (сам, другой):       класс AutoClass (self, other):           проходить       вернуть AutoClass       # В качестве альтернативы, автогенерировать имя класса, а также класс:       # возврат type (сам.__ name__ + прочее.__ name__, (сам, прочее), {})
   def unregister (self):       # classregistry.unregister(self)       print "Отменил бы класс% s сейчас". % self

class MyObject:   __metaclass__ = MyType


class NoneSample (MyObject):   проходить

# Будет напечатан "NoneType None" 
тип печати (NoneSample), repr (NoneSample)

class Example (MyObject):   def __init __ (self, value):       self.value = значение   @make_hook   def add (self, other):       возврат self.__ класс __ (self.value + other.value)

# Отменит регистрацию класса
Example.unregister()

inst = Пример (10)
# Ошибка с атрибутом AttributeError
# Inst.unregister()

print inst + inst
class Sibling (MyObject):   проходить

ExampleSibling = Пример + Sibling
# ExampleSibling теперь является подклассом как Example, так и Sibling (без
#), хотя он будет считать, что он называется "AutoClass",
print ExampleSibling
print ExampleSibling.__ mro__
Код>
1927
ответ дан Thomas Wouters 19 сент. '08 в 10:01
источник поделиться

Классы как объекты

Прежде чем понимать метаклассы, вам нужно освоить классы в Python. И Python имеет очень своеобразное представление о том, какие классы, заимствованные из языка Smalltalk.

В большинстве языков классы - это всего лишь фрагменты кода, описывающие, как создать объект. Это тоже верно для Python:

<Предварительно > <код → → class ObjectCreator (объект): ... проходить ... → > my_object = ObjectCreator() → > печать (my_object) Объект ObjectCreator в 0x8974f2c > Код >

Но классы больше, чем в Python. Классы также являются объектами.

Да, объекты.

Как только вы используете ключевое слово class, Python выполняет его и создает объект. Инструкция

<Предварительно > <код → → class ObjectCreator (объект): ... проходить ... Код >

создает в памяти объект с именем "ObjectCreator".

Этот объект (класс) сам способен создавать объекты (экземпляры), и именно поэтому это класс.

Но все же это объект, и поэтому:

  • вы можете назначить его переменной
  • вы можете скопировать его
  • вы можете добавить к нему атрибуты
  • вы можете передать его как параметр функции

например:.

<Предварительно > <код → → print (ObjectCreator) # вы можете напечатать класс, потому что это объект < class '__main __. ObjectCreator' → → def echo (o): ... print (o) ... → > echo (ObjectCreator) # вы можете передать класс в качестве параметра < class '__main __. ObjectCreator' → → print (hasattr (ObjectCreator, 'new_attribute')) Ложь → > ObjectCreator.new_attribute = 'foo' # вы можете добавлять атрибуты в класс → > print (hasattr (ObjectCreator, 'new_attribute')) Правда → > печать (ObjectCreator.new_attribute) Foo → > ObjectCreatorMirror = ObjectCreator # вы можете назначить класс переменной → > печать (ObjectCreatorMirror.new_attribute) Foo → > печать (ObjectCreatorMirror()) Объект ObjectCreator в 0x8997b4c > Код >

Динамическое создание классов

Поскольку классы являются объектами, вы можете создавать их на лету, как и любой объект.

Сначала вы можете создать класс в функции с помощью class:

<Предварительно > <код → → def choose_class (имя): ... if name == 'foo': ... класс Foo (объект): ... проходить ... return Foo # возвращает класс, а не экземпляр ... еще: ... класс Bar (объект): ... проходить ... возврат бар ... → > Myclass= choose_class ('foo') → > print (MyClass) # функция возвращает класс, а не экземпляр < class '__main __. Foo' → → print (MyClass()) # вы можете создать объект из этого класса < __ main__.Foo объект в 0x89c6d4c & ​​gt; Код >

Но это не так динамично, так как вам все равно придется писать весь класс самостоятельно.

Поскольку классы являются объектами, они должны быть сгенерированы чем-то.

Когда вы используете ключевое слово class, Python автоматически создает этот объект. Но с большинством вещей в Python, он дает вам способ сделать это вручную.

Помните функцию type? Хорошая старая функция, которая позволяет вам знать, что тип объекта:

<Предварительно > <код → → печати (тип (1)) < type 'int' → → печать (тип ( "1" )) < type 'str' → → печать (тип (ObjectCreator)) < type 'type' → → печать (тип (ObjectCreator())) < class '__main __. ObjectCreator' > Код >

Ну, type имеет совершенно разные способности, он также может создавать классы "на лету". type может принимать описание класса в качестве параметров, и вернуть класс.

(Я знаю, глупо, что одна и та же функция может иметь два совершенно разных применения в соответствии с параметрами, которые вы передаете ему. Это проблема из-за обратной совместимость в Python)

type работает следующим образом:

  (имя класса,    кортеж родительского класса (для наследования, может быть пустым),    словарь, содержащий имена и значения атрибутов)
Код>

например:.

<Предварительно > <код → → класс MyShinyClass (объект): ... проходить Код >

можно создать вручную следующим образом:

<Предварительно > <код → → MyShinyclass= type ('MyShinyClass',(), {}) # возвращает объект класса → > печать (MyShinyClass) < class '__main __. MyShinyClass → → print (MyShinyClass()) # создать экземпляр класса < __ main __. Объект MyShinyClass в 0x8997cec > Код >

Вы заметите, что мы используем "MyShinyClass" как имя класса и как переменную для хранения ссылки на класс. Они могут быть разными, но нет причин усложнять ситуацию.

type принимает словарь для определения атрибутов класса. Итак:

<Предварительно > <код → → класс Foo (объект): ... bar = True Код >

Можно перевести на:

<Предварительно > <код → → Foo = type ('Foo',(), {'bar': True}) Код >

И используется как обычный класс:

<Предварительно > <код → → печать (Foo) < class '__main __. Foo' → → печать (Foo.bar) Правда → > f = Foo() → > печать (е) < __ main__.Foo объект при 0x8a9b84c → → печать (f.bar) Правда Код >

И, конечно, вы можете наследовать его, поэтому:

<Предварительно > <код → → класс FooChild (Foo): ... проходить Код >

:

<Предварительно > <код → → FooChild = type ('FooChild', (Foo,), {}) → > печать (FooChild) < class '__main __. FooChild' → → print (FooChild.bar) # bar наследуется от Foo Правда Код >

В конце концов вы захотите добавить методы в свой класс. Просто определите функцию с соответствующей сигнатурой и назначить ее как атрибут.

<Предварительно > <код → → def echo_bar (self): ... печать (self.bar) ... → > FooChild = type ('FooChild', (Foo,), {'echo_bar': echo_bar}) → > hasattr (Foo, 'echo_bar') Ложь → > hasattr (FooChild, 'echo_bar') Правда → > my_foo = FooChild() → > my_foo.echo_bar() Правда Код >

И вы можете добавить еще больше методов после динамического создания класса, так же, как добавлять методы к обычно создаваемому объекту класса.

<Предварительно > <код → → def echo_bar_more (self): ... print ('еще один метод') ... → > FooChild.echo_bar_more = echo_bar_more → > hasattr (FooChild, 'echo_bar_more') Правда Код >

Вы видите, куда мы идем: в Python классы - это объекты, и вы можете динамически создавать класс "на лету".

Это то, что делает Python, когда вы используете ключевое слово class, и он делает это, используя метакласс.

Что такое метаклассы (наконец)

Метаклассы - это "материал", который создает классы.

Вы определяете классы для создания объектов, правильно?

Но мы узнали, что классы Python - это объекты.

Ну, метаклассы - это то, что создает эти объекты. Это классы классов, вы можете представить их следующим образом:

  Myclass= MetaClass()
my_object = MyClass()
Код>

Вы видели, что type позволяет сделать что-то вроде этого:

  Myclass= type ('MyClass',(), {})
Код>

Это потому, что функция type на самом деле является метаклассом. type - это metaclass Python использует для создания всех классов за кулисами.

Теперь вы задаетесь вопросом, почему черта написана в нижнем регистре, а не type?

Ну, я думаю, это вопрос согласованности с str, класс, который создает строковые объекты и int класс, который создает целые объекты. type просто класс, который создает объекты класса.

Вы видите это, проверяя атрибут __ class __.

Все, и я имею в виду все, является объектом в Python. Это включает ints, строк, функций и классов. Все они - объекты. И все они имеют был создан из класса:

<Предварительно > <код → → возраст = 35 → > возраст.__ class__ < type 'int' → → name = 'bob' → > имя.__ class__ < type 'str' → → def foo(): pass → > Foo.__ class__ < type 'function' → → класс Bar (объект): pass → > b = Bar() → > б.__ class__ < class '__main __. Bar' > Код >

Теперь, что такое __ class __ любого __ class __?

<Предварительно > <код → → возраст.__ класс __.__ class__ < type 'type' → → имя.__ класса __.__ class__ < type 'type' → → Foo.__ класс __.__ class__ < type 'type' → → б.__ класс __.__ class__ < type 'type' > Код >

Итак, метакласс - это всего лишь материал, создающий объекты класса.

Вы можете назвать это "фабрикой классов", если хотите.

type - это встроенный метакласс, используемый Python, но, конечно, вы можете создать свой собственный метакласс.

__ metaclass __ атрибут

Вы можете добавить атрибут __ metaclass __ при написании класса:

  класс Foo (объект):   __metaclass__ = что-то...   [...]
Код>

Если вы это сделаете, Python будет использовать метакласс для создания класса Foo.

Осторожно, это сложно.

Сначала вы пишете class Foo (object), но объект класса Foo не создается в памяти еще.

В определении класса Python будет искать __ metaclass __. Если он найдет это, он будет использовать его для создания класса объекта Foo. Если этого не произойдет, он будет использовать type для создания класса.

Прочитайте это несколько раз.

Когда вы выполните:

  класс Foo (Bar):   проходить
Код>

Python делает следующее:

Есть ли атрибут __ metaclass __ в Foo?

Если да, создайте в памяти объект класса (я сказал объект класса, оставайтесь со мной здесь), с именем Foo, используя то, что находится в __ metaclass __.

Если Python не может найти __ metaclass __, он будет искать __ metaclass __ на уровне MODULE и попытаться сделать то же самое (но только для классов, t наследует что угодно, в основном классы старого стиля).

Тогда, если он вообще не может найти какой-либо __ metaclass __, он будет использовать собственный метакласс класса Bar (первый родительский) (который может быть по умолчанию type), чтобы создать объект класса.

Будьте осторожны, если атрибут __ metaclass __ не будет унаследован, метаклассом родительского ( Bar.__ class __) будет. Если Bar используется атрибут __ metaclass __, который создал Bar с type() (а не type.__ new__()), подклассы не наследуют этого поведения.

Теперь большой вопрос: что вы можете добавить в __ metaclass __?

Ответ: что-то, что может создать класс.

А что может создать класс? type или что-либо, что подклассы или использует его.

Пользовательские метаклассы

Основная цель метакласса - автоматически изменить класс, когда он создан.

Обычно вы делаете это для API, где вы хотите создать классы, соответствующие текущий контекст.

Представьте себе глупый пример, в котором вы решите, что все классы в вашем модуле должны иметь свои атрибуты, записанные в верхнем регистре. Существует несколько способов сделайте это, но один из способов - установить __ metaclass __ на уровне модуля.

Таким образом, все классы этого модуля будут созданы с использованием этого метакласса, и мы просто должны сообщить метаклассу, чтобы все атрибуты были в верхнем регистре.

К счастью, __ metaclass __ действительно может быть любым вызываемым, он не должен быть формальный класс (я знаю, что-то с "классом" в его имени не нужно класс, пойдите фигурой... но это полезно).

Итак, мы начнем с простого примера, используя функцию.

  # метаклас автоматически получит тот же аргумент
#, который вы обычно переходите к `type`
def upper_attr (future_class_name, future_class_parents, future_class_attr):    ""     Возвращает объект класса, при этом его атрибут повернут     в верхний регистр.    "" 
   # выберите любой атрибут, который не начинается с '__' и заглавной буквы   uppercase_attr = {}   для имени, val в future_class_attr.items():       если не name.startswith('__'):           uppercase_attr [name.upper()] = val       еще:           uppercase_attr [name] = val
   # let `type` сделать создание класса   return type (future_class_name, future_class_parents, uppercase_attr)

__metaclass__ = upper_attr # это повлияет на все классы в модуле

class Foo(): # global __metaclass__ не будет работать с "объектом", хотя   #, но мы можем определить __metaclass__ здесь вместо этого, чтобы воздействовать только на этот класс   #, и это будет работать с дочерними объектами   bar = 'bip'

print (hasattr (Foo, 'bar'))
# Out: False
print (hasattr (Foo, 'BAR'))
# Out: True

f = Foo()
печать (f.BAR)
# Out: 'bip'
Код>

Теперь давайте сделаем то же самое, но с использованием реального класса для метакласса:

  # помните, что `type` на самом деле является классом типа` str` и `int`
#, чтобы вы могли наследовать его
class UpperAttrMetaclass (тип):   # __new__ - это метод, называемый до __init__   # это метод, который создает объект и возвращает его   # while __init__ просто инициализирует объект, переданный как параметр   # вы редко используете __new__, за исключением случаев, когда вы хотите контролировать, как объект   # создано.   # здесь созданный объект является классом, и мы хотим его настроить   # поэтому мы переопределяем __new__   # вы можете сделать что-то в __init__ тоже, если хотите   # Некоторое расширенное использование включает в себя переопределение __call__, но мы не будем   # видеть это   def __new __ (upperattr_metaclass, future_class_name,               future_class_parents, future_class_attr):
       uppercase_attr = {}       для имени, val в future_class_attr.items():           если не name.startswith('__'):               uppercase_attr [name.upper()] = val           еще:               uppercase_attr [name] = val
       return type (future_class_name, future_class_parents, uppercase_attr)
Код>

Но это не ООП. Мы называем type напрямую, и мы не переопределяем или вызвать родительский __ новый __. Давайте сделаем это:

  class UpperAttrMetaclass (тип):
   def __new __ (upperattr_metaclass, future_class_name,               future_class_parents, future_class_attr):
       uppercase_attr = {}       для имени, val в future_class_attr.items():           если не name.startswith('__'):               uppercase_attr [name.upper()] = val           еще:               uppercase_attr [name] = val
       # повторное использование type.__ новый__ способ       # это основной ООП, ничего волшебного там       return type.__ new __ (upperattr_metaclass, future_class_name,                           future_class_parents, uppercase_attr)
Код>

Возможно, вы заметили дополнительный аргумент upperattr_metaclass. Там есть ничего особенного: __ new __ всегда получает класс, в котором он определен, в качестве первого параметра. Точно так же, как у вас есть self для обычных методов, которые получают экземпляр как первый параметр или определяющий класс для методов класса.

Конечно, имена, которые я использовал здесь, являются длинными для ясности, но, для self, все аргументы имеют обычные имена. Так что реальное производство metaclass будет выглядеть так:

  class UpperAttrMetaclass (тип):
   def __new __ (cls, clsname, base, dct):
       uppercase_attr = {}       для имени, val в dct.items():           если не name.startswith('__'):               uppercase_attr [name.upper()] = val           еще:               uppercase_attr [name] = val
       return type.__ new __ (cls, clsname, base, uppercase_attr)
Код>

Мы можем сделать его еще более чистым, используя super, что облегчит наследование (потому что да, у вас могут быть метаклассы, наследующие от метаклассов, наследуемые от типа):

  class UpperAttrMetaclass (тип):
   def __new __ (cls, clsname, base, dct):
       uppercase_attr = {}       для имени, val в dct.items():           если не name.startswith('__'):               uppercase_attr [name.upper()] = val           еще:               uppercase_attr [name] = val
       return super (UpperAttrMetaclass, cls).__ new __ (cls, clsname, base, uppercase_attr)
Код>

Вот и все. В метаклассах ничего больше нет.

Причина сложности кода с использованием метаклассов заключается не в том, что метаклассов, это потому, что вы обычно используете метаклассы, чтобы делать скрученные вещи полагаясь на интроспекцию, манипулируя наследованием, vars, такие как __ dict __ и т.д.

Действительно, метаклассы особенно полезны для черной магии, и поэтому сложный материал. Но сами по себе они просты:

  • перехватить создание класса
  • изменить класс
  • вернуть измененный класс

Зачем использовать классы метаклассов вместо функций?

Так как __ metaclass __ может принимать любые вызываемые вызовы, почему вы используете класс так как это явно сложнее?

Есть несколько причин для этого:

  • Цель ясно. Когда вы читаете UpperAttrMetaclass (type), вы знаете что будет следовать
  • Вы можете использовать ООП. Metaclass может наследовать от метакласса, переопределять родительские методы. Метаклассы могут даже использовать метаклассы.
  • Подклассы класса будут экземплярами своего метакласса, если вы указали класс метакласса, но не с метаклассовой функцией.
  • Вы можете лучше структурировать свой код. Вы никогда не используете метаклассы для чего-то, как тривиальным, как показано выше. Это обычно для чего-то сложного. Имея способность делать несколько методов и группировать их в одном классе очень полезна чтобы сделать код более удобным для чтения.
  • Вы можете подключиться к __ new __, __ init __ и __ call __. Это позволит вы делаете разные вещи. Даже если обычно вы можете сделать все это в __ new __, некоторым людям просто удобнее использовать __ init __.
  • Они называются метаклассами, черт побери! Это должно означать что-то!

Зачем использовать метаклассы?

Теперь большой вопрос. Почему вы используете какую-то непонятную функцию, подверженную ошибкам?

Ну, обычно вы этого не делаете:

Метаклассы - это более глубокая магия, которая 99% пользователей никогда не должны беспокоиться. Если вам интересно, нужны ли они вам, вы не (люди, которые на самом деле им нужно знать с уверенностью, что они нуждаются в них и не нуждаются в объяснение причин).

Python Guru Tim Peters

Основным вариантом использования метакласса является создание API. Типичным примером этого является ORM Django.

Это позволяет вам определить что-то вроде этого:

  class Person (models.Model):   name = models.CharField(max_length = 30)   age = models.IntegerField()
Код>

Но если вы это сделаете:

  guy = Person (name= 'bob', age = '35 ')
печать (guy.age)
Код>

Он не вернет объект IntegerField. Он вернет код int и может даже взять его непосредственно из базы данных.

Это возможно, потому что models.Model определяет __ metaclass __ и он использует магию, которая превратит Person, который вы только что определили, с помощью простых операторов в сложный крючок в поле базы данных.

Django делает что-то сложное, просто разоблачая простой API и использование метаклассов, воссоздание кода из этого API для выполнения реальной работы за кулисами.

Последнее слово

Сначала вы знаете, что классы - это объекты, которые могут создавать экземпляры.

Ну ведь классы сами являются экземплярами. Из метаклассов.

<Предварительно > <код → → класс Foo (объект): pass → > Идентификатор (Foo) 142630324 Код >

Все является объектом в Python, и все они являются либо экземплярами классов или экземпляры метаклассов.

За исключением type.

type на самом деле является его собственным метаклассом. Это не то, что вы могли бы воспроизводить в чистом Python, и делается путем обмана немного при реализации уровень.

Во-вторых, метаклассы сложны. Вы не можете использовать их для очень простые изменения класса. Вы можете менять классы, используя два разных метода:

В 99% случаев, когда вам требуется изменение класса, вам лучше использовать их.

Но в 98% случаев вам вообще не нужны изменения класса.

5557
ответ дан e-satis 05 июля '11 в 14:29
источник поделиться

Обратите внимание, этот ответ для Python 2.x, как он был написан в 2008 году, метаклассы немного отличаются в 3.x, см. комментарии.

Метаклассы - это секретный соус, который делает работу "класса". Метакласс по умолчанию для нового объекта стиля называется "type".

class type(object)
  |  type(object) -> the object type
  |  type(name, bases, dict) -> a new type

Метаклассы принимают 3 аргумента. ' имя', ' базы и dict'

Вот где начинается секрет. Ищите, где в этом примерном определении класса появляются имя, базы и dict.

class ThisIsTheName(Bases, Are, Here):
    All_the_code_here
    def doesIs(create, a):
        dict

Позволяет определить метакласс, который продемонстрирует, как его вызывает класс:.

def test_metaclass(name, bases, dict):
    print 'The Class Name is', name
    print 'The Class Bases are', bases
    print 'The dict has', len(dict), 'elems, the keys are', dict.keys()

    return "yellow"

class TestName(object, None, int, 1):
    __metaclass__ = test_metaclass
    foo = 1
    def baz(self, arr):
        pass

print 'TestName = ', repr(TestName)

# output => 
The Class Name is TestName
The Class Bases are (<type 'object'>, None, <type 'int'>, 1)
The dict has 4 elems, the keys are ['baz', '__module__', 'foo', '__metaclass__']
TestName =  'yellow'

И теперь, пример, который на самом деле означает что-то, это автоматически сделает переменные в списке "атрибуты", установленные в классе, и установите значение "Нет".

def init_attributes(name, bases, dict):
    if 'attributes' in dict:
        for attr in dict['attributes']:
            dict[attr] = None

    return type(name, bases, dict)

class Initialised(object):
    __metaclass__ = init_attributes
    attributes = ['foo', 'bar', 'baz']

print 'foo =>', Initialised.foo
# output=>
foo => None

Обратите внимание, что магическое поведение, которое "Initalised" получает с помощью метакласса init_attributes, не передается в подкласс Initalised.

Вот еще более конкретный пример, показывающий, как вы можете подклассифицировать "тип", чтобы создать метакласс, который выполняет действие при создании класса. Это довольно сложно:

class MetaSingleton(type):
    instance = None
    def __call__(cls, *args, **kw):
        if cls.instance is None:
            cls.instance = super(MetaSingleton, cls).__call__(*args, **kw)
        return cls.instance

 class Foo(object):
     __metaclass__ = MetaSingleton

 a = Foo()
 b = Foo()
 assert a is b
291
ответ дан Jerub 19 сент. '08 в 9:26
источник поделиться

Одно использование для метаклассов - это добавление новых свойств и методов в экземпляр автоматически.

Например, если вы посмотрите на модели Django, их определение выглядит немного запутанным. Похоже, что вы определяете свойства класса:

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

Однако во время выполнения объекты Person заполняются всякими полезными методами. См. источник для некоторой удивительной метаклассификации.

117
ответ дан Antti Rasinen 19 сент. '08 в 9:45
источник поделиться

Другие объяснили, как работают метаклассы и как они вписываются в систему типа 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, который записывает порядок, в котором были определены классы.

107
ответ дан kindall 21 июня '11 в 19:30
источник поделиться

Я думаю, что введение ONLamp в программирование метакласса хорошо написано и дает действительно хорошее введение в эту тему, несмотря на то, что уже несколько лет.

http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html

Вкратце: класс - это проект создания экземпляра, метакласс - это план создания класса. Легко видеть, что в классах Python тоже должны быть первоклассными объектами, чтобы включить это поведение.

Я никогда не писал его сам, но я думаю, что одно из самых приятных применений метаклассов можно увидеть в структуре Django. Классы моделей используют метаклассический подход, чтобы включить декларативный стиль написания новых моделей или классов классов. В то время как метакласс создает класс, все участники получают возможность настроить сам класс.

Осталось сказать: если вы не знаете, что такое метаклассы, вероятность того, что вы не понадобятся им, составляет 99%.

81
ответ дан Matthias Kestenholz 19 сент. '08 в 9:32
источник поделиться

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

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 выполняет его, он использует метакласс для создания экземпляра объекта класса (который, в свою очередь, будет использоваться для создания экземпляров этого класса).

Так же, как мы можем использовать определения классов для изменения поведения пользовательских объектов, мы можем использовать определение класса метакласса, чтобы изменить способ поведения объекта класса.

Для чего их можно использовать? Из docs:

Потенциальное использование метаклассов безгранично. Некоторые идеи, которые были изучены, включают в себя ведение журнала, проверку интерфейса, автоматическое делегирование, автоматическое создание свойств, прокси, фреймворки и автоматическую блокировку/синхронизацию ресурсов.

Тем не менее, обычно пользователям рекомендуется избегать использования метаклассов, если это абсолютно необходимо.

Вы используете метакласс каждый раз, когда вы создаете класс:

Когда вы пишете определение класса, например, как это,

class Foo(object): 
    'demo'

Вы создаете экземпляр объекта класса.

>>> Foo
<class '__main__.Foo'>
>>> isinstance(Foo, type), isinstance(Foo, object)
(True, True)

Это то же самое, что функциональный вызов type с соответствующими аргументами и присвоение результата переменной этого имени:

name = 'Foo'
bases = (object,)
namespace = {'__doc__': 'demo'}
Foo = type(name, bases, namespace)

Обратите внимание, что некоторые вещи автоматически добавляются в __dict__, то есть в пространство имен:

>>> 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__ - если мы define __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')

Примечание. Этот пример был адаптирован из документации - нового перечисления в стандартной библиотеке.

Итак, что мы сделали, это создать метакласс, создав класс. Мы также можем рассматривать метакласс как и любой другой класс. Он имеет порядок разрешения метода:

>>> 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>})
>>> 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>})
67
ответ дан Aaron Hall 11 авг. '15 в 2:28
источник поделиться

Обновление Python 3

Есть (в этой точке) два ключевых метода в метаклассе:

  • __prepare__ и
  • __new__

__prepare__ позволяет вам создавать настраиваемое сопоставление (например, OrderedDict), которое будет использоваться в качестве пространства имен во время создания класса. Вы должны вернуть экземпляр любого пространства имен, которое вы выберете. Если вы не реализуете __prepare__, используется обычный dict.

__new__ отвечает за фактическое создание/модификацию окончательного класса.

Голые кости, do-nothing-extra metaclass хотели бы:

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
47
ответ дан Ethan Furman 01 марта '16 в 22:48
источник поделиться

Метакласс - это класс, который сообщает, как (какой-то) должен быть создан другой класс.

Это случай, когда я видел метакласс как решение моей проблемы: У меня была действительно сложная проблема, которая, вероятно, могла быть решена по-другому, но я решил решить ее с помощью метакласса. Из-за сложности это один из немногих модулей, которые я написал, когда комментарии в модуле превосходят количество написанного кода. Вот оно...

#!/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 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 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 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()
37
ответ дан Craig 25 февр. '14 в 0:20
источник поделиться

Роль метода 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 a function
result = instance('foo', 'bar')

Последнее возможно, когда вы реализуете магический метод __call__() для класса.

class SomeClass(object):
    # ...
    # some definition here ...
    # ...

    def __call__(self, foo, bar):
        return bar + foo

Метод __call__() вызывается, когда экземпляр класса используется как вызываемый. Но, как мы видели из предыдущих ответов, сам класс является экземпляром метакласса, поэтому, когда мы используем класс как вызываемый (т.е. Когда мы создаем его экземпляр), мы на самом деле вызываем его метод metaclass __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.__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

Мы видим, что метод metaclass __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
30
ответ дан mike_e 13 окт. '16 в 12:21
источник поделиться

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.

22
ответ дан Mushahid Khan 09 авг. '16 в 21:49
источник поделиться

Версия tl; dr

Функция type(obj) предоставляет вам тип объекта.

type() класса - это его метакласс.

Чтобы использовать метакласс:

class Foo(object):
    __metaclass__ = MyMetaClass
19
ответ дан noɥʇʎԀʎzɐɹƆ 27 дек. '17 в 5:21
источник поделиться

Классы Python сами являются объектами, как, например, их метакласса.

Метаклас по умолчанию, который применяется, когда вы определяете классы как:

class foo:
    ...

meta class используются для применения некоторого правила ко всему набору классов. Например, предположим, что вы создаете 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

так или иначе, эти два являются наиболее часто используемыми крючками. метаклассирование является мощным, а выше - нигде и исчерпывающий список применений для метаклассификации.

8
ответ дан Xingzhou Liu 13 июля '17 в 10:58
источник поделиться

Функция type() может возвращать тип объекта или создавать новый тип,

например, мы можем создать класс Hi с помощью функции type() и не использовать этот способ с классом Hi (object):

def func(self, name='mike'):
    print('Hi, %s.' % name)

Hi = type('Hi', (object,), dict(hi=func))
h = Hi()
h.hi()
Hi, mike.

type(Hi)
type

type(h)
__main__.Hi

В дополнение к типу() для динамического создания классов вы можете контролировать поведение поведения класса и использовать метакласс.

В соответствии с объектной моделью Python класс является объектом, поэтому класс должен быть экземпляром другого определенного класса. По умолчанию класс Python является экземпляром класса type. То есть, тип - это метакласс большинства встроенных классов и метакласс пользовательских классов.

class ListMetaclass(type):
    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, value: self.append(value)
        return type.__new__(cls, name, bases, attrs)

class CustomList(list, metaclass=ListMetaclass):
    pass

lst = CustomList()
lst.add('custom_list_1')
lst.add('custom_list_2')

lst
['custom_list_1', 'custom_list_2']

Магия вступает в силу, когда мы передаем аргументы ключевого слова в метаклассе, это означает, что интерпретатор Python создает CustomList через ListMetaclass. new(), на данный момент мы можем изменить определение класса, например, и добавить новый метод, а затем вернуть исправленное определение.

3
ответ дан binbjz 12 янв. '18 в 12:16
источник поделиться

метаклассов. Я могу это проиллюстрировать. Метакласс - это класс, экземпляры которого являются классами. Подобно "обычным" классам, определяется поведение экземпляров класса, метакласс определяет поведение классов и их экземпляров.

Метаклассы не поддерживаются каждым объектно-ориентированным языком программирования. Те языки программирования, которые поддерживают метаклассы, значительно различаются в зависимости от их реализации. Python поддерживает их.

Некоторые программисты видят метаклассы в Python как "решения, ожидающие или ищущие проблему".

Существует множество вариантов использования метаклассов. Просто чтобы назвать несколько:

logging and profiling
interface checking
registering classes at creation time
automatically adding new methods
automatic property creation
proxies
automatic resource locking/synchronization.

Метаклассы определяются как любой другой класс Python, но они являются классами, которые наследуются от типа. Другое отличие состоит в том, что метаклас вызывается автоматически, когда инструкция класса, использующая метакласс, заканчивается. Другими словами: Если ключевое слово "метакласса" не будет передано после базовых классов (также не может быть базовых классов) заголовка класса, будет вызван тип() (т.е. вызов). Если ключевое слово metaclass используется, с другой стороны, назначенный ему класс будет вызываться вместо типа.

Позвольте мне показать вам, создав очень простой метакласс. Это ничего не значит, за исключением того, что он будет печатать содержимое своих аргументов в новом методе и возвращает результаты этого типа. Новый вызов:

класс LittleMeta (тип):   def new (cls, clsname, суперклассы, атрибут):       print ( "clsname:", clsname)       print ( "суперклассы:", суперклассы)       print ( "attributedict:", attributedict)        new (cls, clsname, суперклассы, атрибут)

Я буду использовать метакласс "LittleMeta" в следующем примере:

класс S:   проходить класс A (S, metaclass= LittleMeta):

clsname: A суперклассы: (,) attributedict: {' module': ' main', ' qualname': 'A'}   проходить a = A()

3
ответ дан Sandeep Anand 23 нояб. '17 в 9:46
источник поделиться

Метакласс определяется как "класс класса". Любой класс, экземпляры которого сами являются классами, является метаклассом. Итак, в соответствии с тем, что мы видели выше, это делает тип метаклассом - на самом деле, наиболее часто используемым метаклассом в Python, поскольку он является метаклассом по умолчанию для всех классов.

Так как метакласс является классом класса, он используется для построения классов (так же, как класс используется для построения объектов). Но подождите секунду, не создаем классы со стандартным определением класса? Определенно, но то, что Python делает под капотом, следующее:

  • Когда он видит определение класса, Python выполняет его для сбора атрибуты (включая методы) в словарь
  • Когда определение класса закончено, Python определяет метакласс из класс. Позвольте назвать Meta​​li >
  • В конце концов, Python выполняет Meta (имя, базы, dct), где:

    -Meta - это метакласс, поэтому этот вызов создает его.

    -name - это имя вновь созданного класса

    -bases - это набор базовых классов класса

    -dct сопоставляет имена атрибутов объектам, перечисляя все атрибуты класса

Как определить метакласс класса? Проще говоря, если какой-либо класс или одна из его баз имеет атрибут метакласс, он воспринимается как метакласс. В противном случае введите метакласс.

Итак, что происходит, когда мы определяем:

class MyKlass(object):
  foo = 2

Это: MyKlass не имеет атрибута метакласса, поэтому вместо него используется тип, а создание класса выполняется как:

MyKlass = type(name, bases, dct)

Это соответствует тому, что мы видели в начале статьи. Если, с другой стороны, у MyKlass есть определенный метакласс:

class MyKlass(object):
  __metaclass__ = MyMeta
  foo = 2

Затем создание класса выполняется как:

MyKlass = MyMeta(name, bases, dct)

Итак, MyMeta должен быть соответствующим образом реализован для поддержки такой формы вызова и возврата нового класса. Это фактически похоже на запись нормального класса с предопределенной сигнатурой конструктора.

o управлять созданием и инициализацией класса в метаклассе, вы можете реализовать метод metaclass __new__ и/или конструктор __init__. Большинство реальных метаклассов, вероятно, переопределит только один из них. __new__ должен быть реализован, если вы хотите управлять созданием нового объекта (класса в нашем случае), а __init__ должен быть реализован, когда вы хотите контролировать инициализацию нового объекта после его создания.

Итак, когда вызов MyMeta выполняется выше, что происходит под капотом, это:

MyKlass = MyMeta.__new__(MyMeta, name, bases, dct)
MyMeta.__init__(MyKlass, name, bases, dct)

Вот более конкретный пример, который должен продемонстрировать, что происходит. Пусть записано это определение для метакласса

class MyMeta(type):
    def __new__(meta, name, bases, dct):
        print '-----------------------------------'
        print "Allocating memory for class", name
        print meta
        print bases
        print dct
        return super(MyMeta, meta).__new__(meta, name, bases, dct)
    def __init__(cls, name, bases, dct):
        print '-----------------------------------'
        print "Initializing class", name
        print cls
        print bases
        print dct
        super(MyMeta, cls).__init__(name, bases, dct)

Выполняется со следующим определением класса:

class MyKlass(object):
    __metaclass__ = MyMeta

    def foo(self, param):
        pass

    barattr = 2
1
ответ дан Umer Farooq 11 февр. '18 в 15:10
источник поделиться

Метакласс - это, по сути, абстрактный базовый класс - концепция, преподаваемая в большинстве курсов по компьютерному программированию. Класс связан с метаклассом.

-6
ответ дан JKL 01 июня '17 в 11:01
источник поделиться

Другие вопросы по меткам