Недавно мне задали тот же вопрос, и я дал несколько ответов. Я надеюсь, что можно будет возобновить эту ветку, так как я хотел подробно остановиться на некоторых из упомянутых вариантов использования и добавить несколько новых.
Большинство метаклассов, которые я видел, делают одно из двух:
Регистрация (добавление класса в структуру данных):
models = {}
class ModelMetaclass(type):
def __new__(meta, name, bases, attrs):
models[name] = cls = type.__new__(meta, name, bases, attrs)
return cls
class Model(object):
__metaclass__ = ModelMetaclass
Всякий раз, когда вы создаете подкласс Model
, ваш класс регистрируется в models
словаре:
>>> class A(Model):
... pass
...
>>> class B(A):
... pass
...
>>> models
{'A': <__main__.A class at 0x...>,
'B': <__main__.B class at 0x...>}
Это также можно сделать с помощью декораторов классов:
models = {}
def model(cls):
models[cls.__name__] = cls
return cls
@model
class A(object):
pass
Или с явной функцией регистрации:
models = {}
def register_model(cls):
models[cls.__name__] = cls
class A(object):
pass
register_model(A)
На самом деле, это почти то же самое: вы отрицательно упоминаете декораторы классов, но на самом деле это не более чем синтаксический сахар для вызова функции в классе, поэтому в этом нет никакого волшебства.
В любом случае преимуществом метаклассов в этом случае является наследование, поскольку они работают для любых подклассов, тогда как другие решения работают только для подклассов, явно оформленных или зарегистрированных.
>>> class B(A):
... pass
...
>>> models
{'A': <__main__.A class at 0x...> # No B :(
Рефакторинг (изменение атрибутов класса или добавление новых):
class ModelMetaclass(type):
def __new__(meta, name, bases, attrs):
fields = {}
for key, value in attrs.items():
if isinstance(value, Field):
value.name = '%s.%s' % (name, key)
fields[key] = value
for base in bases:
if hasattr(base, '_fields'):
fields.update(base._fields)
attrs['_fields'] = fields
return type.__new__(meta, name, bases, attrs)
class Model(object):
__metaclass__ = ModelMetaclass
Всякий раз, когда вы подклассифицируете Model
и определяете некоторые Field
атрибуты, они вводятся с их именами (например, для более информативных сообщений об ошибках) и группируются в _fields
словарь (для упрощения итерации без необходимости просматривать все атрибуты класса и все его базовые классы '' атрибуты каждый раз):
>>> class A(Model):
... foo = Integer()
...
>>> class B(A):
... bar = String()
...
>>> B._fields
{'foo': Integer('A.foo'), 'bar': String('B.bar')}
Опять же, это можно сделать (без наследования) с помощью декоратора класса:
def model(cls):
fields = {}
for key, value in vars(cls).items():
if isinstance(value, Field):
value.name = '%s.%s' % (cls.__name__, key)
fields[key] = value
for base in cls.__bases__:
if hasattr(base, '_fields'):
fields.update(base._fields)
cls._fields = fields
return cls
@model
class A(object):
foo = Integer()
class B(A):
bar = String()
# B.bar has no name :(
# B._fields is {'foo': Integer('A.foo')} :(
Или явно:
class A(object):
foo = Integer('A.foo')
_fields = {'foo': foo} # Don't forget all the base classes' fields, too!
Хотя, в отличие от вашего отстаивания читабельного и поддерживаемого немета-программирования, это гораздо более громоздко, избыточно и подвержено ошибкам:
class B(A):
bar = String()
# vs.
class B(A):
bar = String('bar')
_fields = {'B.bar': bar, 'A.foo': A.foo}
Рассмотрев наиболее распространенные и конкретные варианты использования, единственные случаи, когда вам абсолютно НЕОБХОДИМО использовать метаклассы, - это когда вы хотите изменить имя класса или список базовых классов, потому что после определения эти параметры запекаются в классе, а декоратор или функция может их разбить.
class Metaclass(type):
def __new__(meta, name, bases, attrs):
return type.__new__(meta, 'foo', (int,), attrs)
class Baseclass(object):
__metaclass__ = Metaclass
class A(Baseclass):
pass
class B(A):
pass
print A.__name__ # foo
print B.__name__ # foo
print issubclass(B, A) # False
print issubclass(B, int) # True
Это может быть полезно в фреймворках для выдачи предупреждений всякий раз, когда определены классы с похожими именами или неполными деревьями наследования, но я не могу придумать причину, помимо троллинга, для фактического изменения этих значений. Может быть, Дэвид Бизли сможет.
В любом случае, в Python 3 метаклассы также имеют __prepare__
метод, который позволяет вам оценивать тело класса в сопоставлении, отличном от a dict
, таким образом поддерживая упорядоченные атрибуты, перегруженные атрибуты и другие классные злые вещи:
import collections
class Metaclass(type):
@classmethod
def __prepare__(meta, name, bases, **kwds):
return collections.OrderedDict()
def __new__(meta, name, bases, attrs, **kwds):
print(list(attrs))
# Do more stuff...
class A(metaclass=Metaclass):
x = 1
y = 2
# prints ['x', 'y'] rather than ['y', 'x']
class ListDict(dict):
def __setitem__(self, key, value):
self.setdefault(key, []).append(value)
class Metaclass(type):
@classmethod
def __prepare__(meta, name, bases, **kwds):
return ListDict()
def __new__(meta, name, bases, attrs, **kwds):
print(attrs['foo'])
# Do more stuff...
class A(metaclass=Metaclass):
def foo(self):
pass
def foo(self, x):
pass
# prints [<function foo at 0x...>, <function foo at 0x...>] rather than <function foo at 0x...>
Вы можете утверждать, что упорядоченные атрибуты могут быть достигнуты с помощью счетчиков создания, а перегрузка может быть смоделирована с помощью аргументов по умолчанию:
import itertools
class Attribute(object):
_counter = itertools.count()
def __init__(self):
self._count = Attribute._counter.next()
class A(object):
x = Attribute()
y = Attribute()
A._order = sorted([(k, v) for k, v in vars(A).items() if isinstance(v, Attribute)],
key = lambda (k, v): v._count)
class A(object):
def _foo0(self):
pass
def _foo1(self, x):
pass
def foo(self, x=None):
if x is None:
return self._foo0()
else:
return self._foo1(x)
Помимо того, что он гораздо более уродлив, он также менее гибкий: что, если вам нужны упорядоченные буквальные атрибуты, такие как целые числа и строки? Что, если None
допустимое значение для x
?
Вот творческий способ решения первой проблемы:
import sys
class Builder(object):
def __call__(self, cls):
cls._order = self.frame.f_code.co_names
return cls
def ordered():
builder = Builder()
def trace(frame, event, arg):
builder.frame = frame
sys.settrace(None)
sys.settrace(trace)
return builder
@ordered()
class A(object):
x = 1
y = 'foo'
print A._order # ['x', 'y']
А вот творческий способ решить вторую проблему:
_undefined = object()
class A(object):
def _foo0(self):
pass
def _foo1(self, x):
pass
def foo(self, x=_undefined):
if x is _undefined:
return self._foo0()
else:
return self._foo1(x)
Но это намного, НАМНОГО вудуера, чем простой метакласс (особенно первый, который действительно плавит вам мозг). Я хочу сказать, что вы смотрите на метаклассы как на незнакомые и нелогичные, но вы также можете рассматривать их как следующий шаг эволюции языков программирования: вам просто нужно изменить свое мышление. В конце концов, вы, вероятно, могли бы делать все на C, включая определение структуры с указателями на функции и передачу ее в качестве первого аргумента ее функциям. Человек, впервые увидевший C ++, может спросить: «Что это за магия? Почему компилятор неявно передаетthis
методам, а не обычным и статическим функциям? Лучше быть явным и многословным в своих аргументах ". Но тогда объектно-ориентированное программирование станет гораздо более мощным, как только вы его получите; как и это, э ... квази-аспектно-ориентированное программирование, я думаю. понимаете метаклассы, они на самом деле очень простые, так почему бы не использовать их, когда это удобно?
И, наконец, метаклассы классные, а программирование должно доставлять удовольствие. Постоянное использование стандартных программных конструкций и шаблонов проектирования скучно, скучно и мешает вашему воображению. Живи немного! Вот метаметакласс специально для вас.
class MetaMetaclass(type):
def __new__(meta, name, bases, attrs):
def __new__(meta, name, bases, attrs):
cls = type.__new__(meta, name, bases, attrs)
cls._label = 'Made in %s' % meta.__name__
return cls
attrs['__new__'] = __new__
return type.__new__(meta, name, bases, attrs)
class China(type):
__metaclass__ = MetaMetaclass
class Taiwan(type):
__metaclass__ = MetaMetaclass
class A(object):
__metaclass__ = China
class B(object):
__metaclass__ = Taiwan
print A._label # Made in China
print B._label # Made in Taiwan
редактировать
Это довольно старый вопрос, но он все еще получает положительные отзывы, поэтому я подумал, что добавлю ссылку на более подробный ответ. Если вы хотите узнать больше о метаклассах и их использовании, я только что опубликовал статью об этом здесь .