Python 高手编程系列七百三十二:Python 3 中新的元类语法

元类并不是新的 Python 特性,从 Python 2.2 版开始就一直都有。不过它的语法发生了
重大变化,这种变化既不向后兼容也不向前兼容。新的语法如下所示:
class ClassWithAMetaclass(metaclass=type):
pass
在 Python 2 中,其写法必须是这样的:
class ClassWithAMetaclass(object):
metaclass = type
Python 2 的 class 语句不接受关键字参数,所以 Python 3 定义元类的语法会在导入时
引发 SyntaxError 异常。仍然可以编写在两个 Python 版本中都能运行的元类代码,但需
要做一些额外工作。幸运的是,与兼容性相关的包(例如 six)为这一问题提供了简单又
可复用的解决方法,如下所示:
from six import with_metaclass
class Meta(type):
pass
class Base(object):
pass
class MyClass(with_metaclass(Meta, Base)):
pass
还有一点重要的区别,就是 Python 2 的元类没有__prepare__()钩子(hook)。在
Python 2 中实现这一函数不会引发任何异常,但是没有任何意义,因为它不会被调用以提供干净的命名空间对象。因此,如果一个包想要保持 Python 2 的兼容性,那么就需要依赖
更复杂的技巧来完成用__prepare__()可以轻松完成的工作。例如,Django REST 框架
(http://www.django-rest-framework.org)使用下列方法来保存一个类中属性的添加顺序:
class SerializerMetaclass(type):
@classmethod
def _get_declared_fields(cls, bases, attrs):
fields = [(field_name, attrs.pop(field_name))
for field_name, obj in list(attrs.items())
if isinstance(obj, Field)]
fields.sort(key=lambda x: x[1]._creation_counter)

如果这个类是另一个序列器(Serializer)的子类,

那么添加该序列器的域(fields)。

注意,我们是"逆序"遍历基类(bases),

这是为了保持域的正确顺序。

for base in reversed(bases):
if hasattr(base, ‘_declared_fields’):
fields = list(base._declared_fields.items()) +
fields
return OrderedDict(fields)
def new(cls, name, bases, attrs):
attrs['declared_fields’] = cls.get_declared_fields(
bases, attrs
)
return super(SerializerMetaclass, cls).new(
cls, name, bases, attrs
)
如果默认的命名空间类型(即 dict)不能保证保存键/值元组的顺序,那么上面是一
种变通解决方法。Field 类的每个实例都应该有 creation_counter 属性。这个
Field.creation_counter 属 性 的 创 建 方 式 与 new() 方 法 一 节 介 绍 的
InstanceCountingClass.instance_number 相同。这是一种相当复杂的解决方法,
它在两个不同的类之间共享其实现,这破坏了“单一职责”的原则,其目的只是为了确保
属性的可追踪顺序。在 Python 3 中,这个问题很简单,因为__prepare
()可以返回其他
映射类型(例如 OrderedDict):
from collections import OrderedDict
class OrderedMeta(type):
@classmethod
def prepare(cls, name, bases, **kwargs):
return OrderedDict()
def new(mcs, name, bases, namespace):
namespace[‘order_of_attributes’] = list(namespace.keys())
return super().new(mcs, name, bases, namespace)
class ClassWithOrder(metaclass=OrderedMeta):
first = 8
second = 2
你会看到如下的输出结果:

ClassWithOrderedAttributes.order_ of _attributes
[’ __module __‘, ’ __qualname __’, ‘first’, ‘second’]
ClassWithOrderedAttributes. __dict _.keys()
dict
_keys([’ __dict __‘, ‘first’, ’ __weakref _', ‘second’,
'order
of _attributes’, ’ __module __‘, ’ __doc __’])
元类的使用
一旦掌握了元类,它是一种非常强大的特性,但总是会使代码更加复杂。在将其用于
任意类型的类时,这可能也会降低代码的鲁棒性。例如,如果类中使用了槽、或者一些基
类已经实现了一个有冲突的元类,那么你可能会遇到不好的交互。它们只是没有构造好。
对于修改读/写属性或添加新属性之类的简单操作,可以避免使用元类,而采用更简单
的解决方法,例如 property、描述符或类装饰器。
通常来说,元类也可以用其他更简单的方法来代替,但有些情况下,没有元类可能很
难轻松完成某些事情。举个例子,如果没有大量使用元类,很难想象如何构造 Django 的
ORM 实现。这是可能的,但那样的解决方法可能不会这么好用。框架是元类真正适合使用
的地方。它通常有许多复杂的解决方法,并不容易理解和掌握,但最终来看,其他程序员
可以用来编写更加简洁且可读性更高的代码,其运行的抽象层次更高。

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值