最近有人问我同样的问题,并提出了几个答案。我希望可以重新启动该线程,因为我想详细说明所提到的一些用例,并添加一些新用例。
我见过的大多数元类都执行以下两项操作之一:
注册(将类添加到数据结构中):
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__
方法,该方法使您可以将类主体评估为除之外的映射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
方法,而不是常规和静态函数?最好是对参数进行露骨和冗长。”但是,一旦获得面向对象的编程,它就会变得功能强大得多;嗯,我想……准面向方面的编程。了解元类,它们实际上非常简单,那么为什么不方便使用它们呢?
最后,元类是rad,编程应该很有趣。始终使用标准的编程构造和设计模式既无聊又无济于事,并且阻碍了您的想象力。坚持一下!这是一个元数据类,仅供您使用。
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
编辑
这是一个很老的问题,但仍在投票中,因此我想添加一个指向更全面答案的链接。如果您想了解有关元类及其使用的更多信息,我刚刚在这里发表了一篇有关元类的文章。