1. 元类编程
元类是产生类的类,元类编程是一种使用这一特性,通过定制元类实现元编程的方法.也是python中最"正统"的元编程方式
本节需要的先验知识有:
1.1. type--类工厂
有时,我觉得应该有类似nametuple
的工厂函数,用于创建可变对象.假设我在编写一个宠物店应用程序,我想把狗的数据当作简单的记录处理.编写下面的样板代码让人厌烦:
class Dog: def __init__(self, name, weight, owner): self.name = name self.weight = weight self.owner = owner
rex = Dog('Rex', 30, 'Bob') rex
<__main__.Dog at 0x4d11a58>
各个字段名称出现了三次.写了这么多样板代码,甚至字符串表示形式都不友好.
参考namedtuple
,下面我们创建一个record_factory
函数,即时创建简单的类
def record_factory(cls_name, field_names): try: field_names = field_names.replace(',', ' ').split() except AttributeError: # 不能调用.replace或.split方法 pass # 假定field_names本就是标识符组成的序列 field_names = tuple(field_names) def __init__(self, *args, **kwargs): attrs = dict(zip(self.__slots__, args)) attrs.update(kwargs) for name, value in attrs.items(): setattr(self, name, value) def __iter__(self): for name in self.__slots__: yield getattr(self, name) def __repr__(self): values = ', '.join('{}={!r}'.format(*i) for i in zip(self.__slots__, self)) return '{}({})'.format(self.__class__.__name__, values) cls_attrs = dict(__slots__ = field_names, __init__ = __init__,__iter__ = __iter__,__repr__ = __repr__) return type(cls_name, (object,), cls_attrs)
Cat = record_factory('Cat', 'name weight owner')
rex = Cat('Rex', 30, 'Bob') rex
Cat(name='Rex', weight=30, owner='Bob')
name, weight, _ = rex name, weight
('Rex', 30)
"{2}'s dog weighs {1}kg".format(*rex)
rex = Dog('Rex', 30, 'Bob') rex
0
rex = Dog('Rex', 30, 'Bob') rex
1
rex = Dog('Rex', 30, 'Bob') rex
2
rex = Dog('Rex', 30, 'Bob') rex
3
rex = Dog('Rex', 30, 'Bob') rex
4
可以看出上面的工厂函数核心就在于type()
的使用.通常,我们把type
视作函数,因为我们像函数那样使用它,例如,调用type(my_object)
获取对象所属的类——作用与my_object.__class__
相同.
然而,type
是一个类.当成类使用时,传入三个参数可以新建一个类:
rex = Dog('Rex', 30, 'Bob') rex
5
type的三个参数分别是name
、bases
和dict
.最后一个参数是一个映射,指定新类的属性名和值.
1.2. 元类
元类是制造类的工厂,不过不是函数而是类.
根据Python对象模型,类是对象,因此类肯定是另外某个类的实例.默认情况下,Python中的类是type类的实例.也就是说,type是大多数内置的类和用户定义的类的元类.
rex = Dog('Rex', 30, 'Bob') rex
6
rex = Dog('Rex', 30, 'Bob') rex
7
rex = Dog('Rex', 30, 'Bob') rex
8
rex = Dog('Rex', 30, 'Bob') rex
9
<__main__.Dog at 0x4d11a58>
0
rex = Dog('Rex', 30, 'Bob') rex
9
为了避免无限回溯,type 是其自身的实例,如最后一行所示.
注意,我没有说str或其他对象继承自type.我的意思是,str和其他对象是type的实例.这两个类是object的子类.下图是他们的关系
两个示意图都是正确的.左边的示意图强调str
、type
和LineItem
是object
的子类.右边的示意图则清楚地表明str
、object
和LineItem
是type
的实例.因为它们都是类.
除了type,标准库中还有一些别的元类.例如ABCMeta
和Enum
.如下述代码片段所示,collections.Iterable
所属的类是abc.ABCMeta
.Iterable
是抽象类,而ABCMeta
不是--不管怎样,Iterable
是ABCMeta
的实例.
<__main__.Dog at 0x4d11a58>
2
<__main__.Dog at 0x4d11a58>
3
<__main__.Dog at 0x4d11a58>
4
<__main__.Dog at 0x4d11a58>
5
向上追溯,ABCMeta
最终所属的类也是type
.所有类都直接或间接地是type
的实例,不过只有元类同时也是type
的子类.若想理解元类,一定要知道这种关系:元类(如ABCMeta
)从type
类继承了构建类的能力.
我们要抓住的重点是,所有类都是type的实例,但是元类还是type
的子类,因此可以作为制造类的工厂.具体来说,元类可以通过实现__init__
方法定制实例.元类的__init__
方法可以做到类装饰器能做的任何事情,但是作用更大.
<__main__.Dog at 0x4d11a58>
6
<__main__.Dog at 0x4d11a58>
7
<__main__.Dog at 0x4d11a58>
8
<__main__.Dog at 0x4d11a58>
9
def record_factory(cls_name, field_names): try: field_names = field_names.replace(',', ' ').split() except AttributeError: # 不能调用.replace或.split方法 pass # 假定field_names本就是标识符组成的序列 field_names = tuple(field_names) def __init__(self, *args, **kwargs): attrs = dict(zip(self.__slots__, args)) attrs.update(kwargs) for name, value in attrs.items(): setattr(self, name, value) def __iter__(self): for name in self.__slots__: yield getattr(self, name) def __repr__(self): values = ', '.join('{}={!r}'.format(*i) for i in zip(self.__slots__, self)) return '{}({})'.format(self.__class__.__name__, values) cls_attrs = dict(__slots__ = field_names, __init__ = __init__,__iter__ = __iter__,__repr__ = __repr__) return type(cls_name, (object,), cls_attrs)
0
def record_factory(cls_name, field_names): try: field_names = field_names.replace(',', ' ').split() except AttributeError: # 不能调用.replace或.split方法 pass # 假定field_names本就是标识符组成的序列 field_names = tuple(field_names) def __init__(self, *args, **kwargs): attrs = dict(zip(self.__slots__, args)) attrs.update(kwargs) for name, value in attrs.items(): setattr(self, name, value) def __iter__(self): for name in self.__slots__: yield getattr(self, name) def __repr__(self): values = ', '.join('{}={!r}'.format(*i) for i in zip(self.__slots__, self)) return '{}({})'.format(self.__class__.__name__, values) cls_attrs = dict(__slots__ = field_names, __init__ = __init__,__iter__ = __iter__,__repr__ = __repr__) return type(cls_name, (object,), cls_attrs)
1
1.3. 元类的定义和使用:
元类继承自type
,行为通过实现
__new__(meta,name,bases,class_dict)
类似于类中的
__new__
,用于定义元类的创建行为__init__(cls, name, bases,attr_dict)
类似于类中的
__init__
,用于初始化元类,通过元类产生类时会用到.__call__(cls)
定义类实例化时的行为.
类方法
__prepare__(meta, name, bases)
解释器调用元类的
__new__
方法之前会先调用__prepare__
方法,使用类定义体中的属性创建映射.__prepare__
方法的第一个参数是元类,随后两个参数分别是要构建的类的名称和基类组成的元组,返回值必须是映射.元类构建新类时,__prepare__
方法返回的映射会传给__new__
方法的最后一个参数,然后再传给__init__
方法.
使用元类的类实例化产出类的顺序是:
meta.__prepare__
meta.__new__
meta.__init__
类实例化对象的顺序是:
clz.__call__
clz.__new__
clz.__init__
def record_factory(cls_name, field_names): try: field_names = field_names.replace(',', ' ').split() except AttributeError: # 不能调用.replace或.split方法 pass # 假定field_names本就是标识符组成的序列 field_names = tuple(field_names) def __init__(self, *args, **kwargs): attrs = dict(zip(self.__slots__, args)) attrs.update(kwargs) for name, value in attrs.items(): setattr(self, name, value) def __iter__(self): for name in self.__slots__: yield getattr(self, name) def __repr__(self): values = ', '.join('{}={!r}'.format(*i) for i in zip(self.__slots__, self)) return '{}({})'.format(self.__class__.__name__, values) cls_attrs = dict(__slots__ = field_names, __init__ = __init__,__iter__ = __iter__,__repr__ = __repr__) return type(cls_name, (object,), cls_attrs)
2
def record_factory(cls_name, field_names): try: field_names = field_names.replace(',', ' ').split() except AttributeError: # 不能调用.replace或.split方法 pass # 假定field_names本就是标识符组成的序列 field_names = tuple(field_names) def __init__(self, *args, **kwargs): attrs = dict(zip(self.__slots__, args)) attrs.update(kwargs) for name, value in attrs.items(): setattr(self, name, value) def __iter__(self): for name in self.__slots__: yield getattr(self, name) def __repr__(self): values = ', '.join('{}={!r}'.format(*i) for i in zip(self.__slots__, self)) return '{}({})'.format(self.__class__.__name__, values) cls_attrs = dict(__slots__ = field_names, __init__ = __init__,__iter__ = __iter__,__repr__ = __repr__) return type(cls_name, (object,), cls_attrs)
3
def record_factory(cls_name, field_names): try: field_names = field_names.replace(',', ' ').split() except AttributeError: # 不能调用.replace或.split方法 pass # 假定field_names本就是标识符组成的序列 field_names = tuple(field_names) def __init__(self, *args, **kwargs): attrs = dict(zip(self.__slots__, args)) attrs.update(kwargs) for name, value in attrs.items(): setattr(self, name, value) def __iter__(self): for name in self.__slots__: yield getattr(self, name) def __repr__(self): values = ', '.join('{}={!r}'.format(*i) for i in zip(self.__slots__, self)) return '{}({})'.format(self.__class__.__name__, values) cls_attrs = dict(__slots__ = field_names, __init__ = __init__,__iter__ = __iter__,__repr__ = __repr__) return type(cls_name, (object,), cls_attrs)
4
def record_factory(cls_name, field_names): try: field_names = field_names.replace(',', ' ').split() except AttributeError: # 不能调用.replace或.split方法 pass # 假定field_names本就是标识符组成的序列 field_names = tuple(field_names) def __init__(self, *args, **kwargs): attrs = dict(zip(self.__slots__, args)) attrs.update(kwargs) for name, value in attrs.items(): setattr(self, name, value) def __iter__(self): for name in self.__slots__: yield getattr(self, name) def __repr__(self): values = ', '.join('{}={!r}'.format(*i) for i in zip(self.__slots__, self)) return '{}({})'.format(self.__class__.__name__, values) cls_attrs = dict(__slots__ = field_names, __init__ = __init__,__iter__ = __iter__,__repr__ = __repr__) return type(cls_name, (object,), cls_attrs)
5
def record_factory(cls_name, field_names): try: field_names = field_names.replace(',', ' ').split() except AttributeError: # 不能调用.replace或.split方法 pass # 假定field_names本就是标识符组成的序列 field_names = tuple(field_names) def __init__(self, *args, **kwargs): attrs = dict(zip(self.__slots__, args)) attrs.update(kwargs) for name, value in attrs.items(): setattr(self, name, value) def __iter__(self): for name in self.__slots__: yield getattr(self, name) def __repr__(self): values = ', '.join('{}={!r}'.format(*i) for i in zip(self.__slots__, self)) return '{}({})'.format(self.__class__.__name__, values) cls_attrs = dict(__slots__ = field_names, __init__ = __init__,__iter__ = __iter__,__repr__ = __repr__) return type(cls_name, (object,), cls_attrs)
6
def record_factory(cls_name, field_names): try: field_names = field_names.replace(',', ' ').split() except AttributeError: # 不能调用.replace或.split方法 pass # 假定field_names本就是标识符组成的序列 field_names = tuple(field_names) def __init__(self, *args, **kwargs): attrs = dict(zip(self.__slots__, args)) attrs.update(kwargs) for name, value in attrs.items(): setattr(self, name, value) def __iter__(self): for name in self.__slots__: yield getattr(self, name) def __repr__(self): values = ', '.join('{}={!r}'.format(*i) for i in zip(self.__slots__, self)) return '{}({})'.format(self.__class__.__name__, values) cls_attrs = dict(__slots__ = field_names, __init__ = __init__,__iter__ = __iter__,__repr__ = __repr__) return type(cls_name, (object,), cls_attrs)
7
def record_factory(cls_name, field_names): try: field_names = field_names.replace(',', ' ').split() except AttributeError: # 不能调用.replace或.split方法 pass # 假定field_names本就是标识符组成的序列 field_names = tuple(field_names) def __init__(self, *args, **kwargs): attrs = dict(zip(self.__slots__, args)) attrs.update(kwargs) for name, value in attrs.items(): setattr(self, name, value) def __iter__(self): for name in self.__slots__: yield getattr(self, name) def __repr__(self): values = ', '.join('{}={!r}'.format(*i) for i in zip(self.__slots__, self)) return '{}({})'.format(self.__class__.__name__, values) cls_attrs = dict(__slots__ = field_names, __init__ = __init__,__iter__ = __iter__,__repr__ = __repr__) return type(cls_name, (object,), cls_attrs)
8
1.4. 元类的基本用途
一般来说能不用元类就别用元类,或者说元编程的部分都是这个原则,能不用就别用,但很多时候为了实现一些特殊功能我们不得不用元类来实现
1.4.1. 用来验证子类
元类的最简单用途就是用来验证其子类是否定义正确.构建复杂类体系时我们可能需要确保类风格一致,确保某些方法得到了覆写,或者确保类属性之间具有某些严格的关系.
元类提供了一种可靠的验证方式,每当开发者定义新类时,他会运行验证代码,确保符合规定.
实现这个功能并非必须使用元类,可以在__init__
中写验证代码,在类初始化的时候验证,但如果想构建的时候就验证,那就需要使用元类了.
例: 确保类及其子类定义的图形边数大于3:
def record_factory(cls_name, field_names): try: field_names = field_names.replace(',', ' ').split() except AttributeError: # 不能调用.replace或.split方法 pass # 假定field_names本就是标识符组成的序列 field_names = tuple(field_names) def __init__(self, *args, **kwargs): attrs = dict(zip(self.__slots__, args)) attrs.update(kwargs) for name, value in attrs.items(): setattr(self, name, value) def __iter__(self): for name in self.__slots__: yield getattr(self, name) def __repr__(self): values = ', '.join('{}={!r}'.format(*i) for i in zip(self.__slots__, self)) return '{}({})'.format(self.__class__.__name__, values) cls_attrs = dict(__slots__ = field_names, __init__ = __init__,__iter__ = __iter__,__repr__ = __repr__) return type(cls_name, (object,), cls_attrs)
9
Cat = record_factory('Cat', 'name weight owner')
0
Cat = record_factory('Cat', 'name weight owner')
1
Cat = record_factory('Cat', 'name weight owner')
2
1.4.2. 用来注册子类
元类的另一个用途是在程序中自动注册类型,对于需要反向查找(reverse lookup)的场合会有用.它使我们可以在简单的标识符与对应的类之间建立映射.
例: 我们希望使用下面的这个类将python对象表示为json格式的序列化数据.但同时我们希望可以反序列化,这就要用到元类了.
Cat = record_factory('Cat', 'name weight owner')
3
Cat = record_factory('Cat', 'name weight owner')
4
Cat = record_factory('Cat', 'name weight owner')
5
Cat = record_factory('Cat', 'name weight owner')
6
Cat = record_factory('Cat', 'name weight owner')
7
Cat = record_factory('Cat', 'name weight owner')
8
Cat = record_factory('Cat', 'name weight owner')
9
rex = Cat('Rex', 30, 'Bob') rex
0
1.4.3. 用来与描述符结合使用注解属性
用来解决LineItem
倒数第二版问题的另一个方法就是使用元类
rex = Cat('Rex', 30, 'Bob') rex
1
rex = Cat('Rex', 30, 'Bob') rex
2
用户级别的代码只需继承Entity
类,Validated
字段就能自动获得储存属性的名称.
rex = Cat('Rex', 30, 'Bob') rex
3
rex = Cat('Rex', 30, 'Bob') rex
4
rex = Cat('Rex', 30, 'Bob') rex
5
rex = Cat('Rex', 30, 'Bob') rex
6
rex = Cat('Rex', 30, 'Bob') rex
7
如前所述type
构造方法及元类的__new__
和__init__
方法都会收到要计算的类的定义体,形式是名称到属性的映像.然而在默认情况下,那个映射是字典;也就是说元类或类装饰器获得映射时,属性在类定义体中的顺序已经丢失了.
这个问题的解决办法是,使用Python3引入的特殊方法__prepare__
.解释器调用元类的__new__
方法之前会先调用__prepare__
方法,使用类定义体中的属性创建映射.
__prepare__
方法的第一个参数是元类,随后两个参数分别是要构建的类的名称和基类组成的元组,返回值必须是映射.
元类构建新类时,__prepare__
方法返回的映射会传给__new__
方法的最后一个参数,然后再传给__init__
方法.
rex = Cat('Rex', 30, 'Bob') rex
8
rex = Cat('Rex', 30, 'Bob') rex
3
Cat(name='Rex', weight=30, owner='Bob')
0
Cat(name='Rex', weight=30, owner='Bob')
1
还没有评论,来说两句吧...