A mesma pergunta me foi feita recentemente e encontrei várias respostas. Espero que não haja problema em reviver este tópico, pois gostaria de elaborar alguns dos casos de uso mencionados e adicionar alguns novos.
A maioria das metaclasses que vi fazer uma de duas coisas:
Registro (adicionando uma classe a uma estrutura de dados):
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
Sempre que você cria uma subclasse Model
, sua classe é registrada no models
dicionário:
>>> class A(Model):
... pass
...
>>> class B(A):
... pass
...
>>> models
{'A': <__main__.A class at 0x...>,
'B': <__main__.B class at 0x...>}
Isso também pode ser feito com decoradores de classe:
models = {}
def model(cls):
models[cls.__name__] = cls
return cls
@model
class A(object):
pass
Ou com uma função de registro explícita:
models = {}
def register_model(cls):
models[cls.__name__] = cls
class A(object):
pass
register_model(A)
Na verdade, isso é praticamente o mesmo: você menciona decoradores de classe de maneira desfavorável, mas na verdade não é nada mais do que um açúcar sintático para uma invocação de função em uma classe, então não há mágica nisso.
De qualquer forma, a vantagem das metaclasses neste caso é a herança, pois elas funcionam para quaisquer subclasses, enquanto as outras soluções só funcionam para subclasses explicitamente decoradas ou registradas.
>>> class B(A):
... pass
...
>>> models
{'A': <__main__.A class at 0x...> # No B :(
Refatoração (modificação de atributos de classe ou adição de novos):
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
Sempre que você cria uma subclasse Model
e define alguns Field
atributos, eles são injetados com seus nomes (para mensagens de erro mais informativas, por exemplo) e agrupados em um _fields
dicionário (para facilitar a iteração, sem ter que examinar todos os atributos da classe e todas as suas classes básicas ' atributos todas as vezes):
>>> class A(Model):
... foo = Integer()
...
>>> class B(A):
... bar = String()
...
>>> B._fields
{'foo': Integer('A.foo'), 'bar': String('B.bar')}
Novamente, isso pode ser feito (sem herança) com um decorador de classe:
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')} :(
Ou explicitamente:
class A(object):
foo = Integer('A.foo')
_fields = {'foo': foo} # Don't forget all the base classes' fields, too!
Embora, ao contrário de sua defesa de não metaprogramação legível e sustentável, isso é muito mais complicado, redundante e sujeito a erros:
class B(A):
bar = String()
# vs.
class B(A):
bar = String('bar')
_fields = {'B.bar': bar, 'A.foo': A.foo}
Tendo considerado os casos de uso mais comuns e concretos, os únicos casos em que você TEM absolutamente que usar metaclasses são quando você deseja modificar o nome da classe ou a lista de classes base, porque uma vez definidos, esses parâmetros são incorporados à classe, e nenhum decorador ou função pode desfazê-los.
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
Isso pode ser útil em estruturas para emitir avisos sempre que classes com nomes semelhantes ou árvores de herança incompletas são definidas, mas não consigo pensar em uma razão além de trollagem para realmente alterar esses valores. Talvez David Beazley possa.
De qualquer forma, no Python 3, as metaclasses também têm o __prepare__
método, que permite avaliar o corpo da classe em um mapeamento diferente de um dict
, suportando assim atributos ordenados, atributos sobrecarregados e outras coisas legais perversas:
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...>
Você pode argumentar que os atributos ordenados podem ser obtidos com contadores de criação e a sobrecarga pode ser simulada com argumentos padrão:
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)
Além de ser muito mais feio, também é menos flexível: e se você quiser atributos literais ordenados, como inteiros e strings? E se None
for um valor válido para x
?
Esta é uma maneira criativa de resolver o primeiro problema:
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']
E aqui está uma maneira criativa de resolver o segundo:
_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)
Mas isso é muito, MUITO vodu-er do que uma simples metaclasse (especialmente a primeira, que realmente derrete seu cérebro). Meu ponto é, você vê as metaclasses como estranhas e contra-intuitivas, mas você também pode vê-las como o próximo passo da evolução nas linguagens de programação: você só precisa ajustar sua mentalidade. Afinal, você provavelmente poderia fazer tudo em C, incluindo definir uma estrutura com ponteiros de função e passá-la como o primeiro argumento para suas funções. Uma pessoa vendo C ++ pela primeira vez pode dizer: "o que é essa mágica? Por que o compilador está passando implicitamentethis
aos métodos, mas não às funções regulares e estáticas? É melhor ser explícito e prolixo sobre seus argumentos ". Mas então, a programação orientada a objetos é muito mais poderosa quando você a entende; e isso é, uh ... programação quase orientada a aspectos, eu acho. E assim que você entender metaclasses, elas são realmente muito simples, então por que não usá-las quando for conveniente?
E, finalmente, as metaclasses são radicais e a programação deve ser divertida. Usar construções de programação padrão e padrões de projeto o tempo todo é enfadonho e pouco inspirador e atrapalha sua imaginação. Viva um pouco! Aqui está uma metametaclasse, só para você.
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
Editar
Esta é uma pergunta muito antiga, mas ainda está recebendo votos positivos, então pensei em adicionar um link para uma resposta mais abrangente. Se você gostaria de ler mais sobre metaclasses e seus usos, acabei de publicar um artigo sobre isso aqui .