From 流畅的Python 人民邮电出版社 21章类元编程

类元编程是指在运行时创建或者定制类,可以在任何时候用函数新建类,而无需使用class关键字。

类工厂函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def record_factory(cls_name, field_names):
try:
field_names = field_names.replace(',', ' ').split()
except AttributeError:
pass
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)
# __slots__限制该class能添加的属性,只有在__slots__出现的才会在实例中出现和使用
# 而且__slots__定义后,实例不会有__dict__属性,slots出现的属性都会以元组形式存储
cls_attrs = dict(__slots__ = field_names, __init__ = __init__, __iter__ = __iter__, __repr__ = __repr__)
# 构建新类,然后返回,参数是类名,继承的类,和属性名和值的字典
return type(cls_name, (object, ), cls_attrs)

type是一个类,也是一个函数,当成函数的话,调用type(my_object)返回对象所属的类,作用和my_object.__class__相同。当成类的话,传入所需要的参数,会返回创建出来的新的类。

类装饰器

使用类装饰器或者元类可以改变类的一些属性和定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@entity
class LineItem:
description = NonBlank()
weight = Quantity()
def __init__(self, description, weight):
self.description = description
self.weight = weight
def subtotal(self):
return self.weight * self.price
def entity(cls):
# 对类属性进行的处理
for key, attr in cls.__dict__.items():
if isinstance(attr, Validated):
# 获取到这个属性的类名,其实就是描述符的类名
# 属性是描述符实例,要修改其storage_name
# key就是当前在该类中的属性名
type_name = type(attr).__name__
attr.storage_name = '_{}#{}'.format(type_name, key)
return cls

类装饰器可以做到创建类的时候定制类,但是它只对直接依附的类有效。

导入时和运行时的比较

导入的时候,解释器会从上到下一次性解析完py模块的源码,然后生成用于执行的字节码。如果本地的__pycache__文件夹有最新的pyc文件,解释器就会直接使用这里的字节码。

编译是导入时的活动,但是导入时候有些语句还可能执行某些用户代码,比如import,会运行导入模块中的全部顶层代码。

顶层代码:解释器会执行顶层的def语句,解释器会编译函数的定义体,但是不会执行函数的定义体,就是导入时候定义顶层函数,运行时候才会执行函数定义体

类,导入的时候会执行每个类的定义体,定义类的属性和方法,并且构建类对象,类的定义体是顶层代码,在导入时候运行。

对于装饰器,在导入的时候,也会执行其装饰器里面的顶层代码,因为它装饰了其他函数,就相当于运行了里面的顶层代码。

在运行一段程序时候,先执行导入,导入执行的代码,在运行时候就不会运行,因为已经绑定,直接使用。并且类装饰器不会对子类产生影响,除非子类调用了super()

元类

元类是构建类的类,还是一种类,所以会被实例化等。因为类也是对象,所以类也会是另外一个类的实例。一般来说,Python中的类是type类的实例。type类是大部分类的元类。即int.__class__<class 'type'>。避免无限回溯,type是自身的实例。

int是type类的实例,但是int是object的子类,而object类也是type类的实例。而object是大部分类的父类,所以type也是object的子类。

type和object的关系,还有type是自身的实例这两点都很神奇而且很难说明白。

自己定义的元类,既是type的实例,也是type的子类,因为自己定义的元类从type继承了构建类的能力。元类可以实现__init__方法定制实例。

在元类初始化的时候对类进行修改(也就是对实例进行修改),因为类是元类的实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MetaAleph(type):
# 这个类是type类的子类,因此它也是元类
# cls要初始化的类对象,类名,基类,属性字典
def __init__(cls, name, bases, dic):
# self是创建的类的实例
def inner_2(self):
print("inner")
cls.method_z = inner_2
class ClassFive(metaclass=MetaAleph):
def __init__(self):
print("class five init")
def method_z(self):
print("class five method_z")
# 都会调用MetaAleph的init方法
class ClassSix(ClassFive):
def method_z(self):
print('class six method_z')

只能调用静态方法,而不能实例化的例子:

1
2
3
4
5
6
7
8
9
10
11
class NoInstances(type):
# 将后面的name, bases, dic省略
# 第一个参数都是cls,因为类是元类的实例
def __call__(cls, *args, **kwargs):
raise TypeError("Can't instantiate directly")
# Example
class Spam(metaclass=NoInstances):
@staticmethod
def grok(x):
print('Spam.grok')

实现单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Singleton(type):
def __init__(cls, *args, **kwargs):
cls.__instance = None
super().__init__(*args, **kwargs)
def __call__(cls, *args, **kwargs):
if cls.__instance is None:
cls.__instance = super().__call__(*args, **kwargs)
return cls.__instance
else:
return cls.__instance
# Example
class Spam(metaclass=Singleton):
def __init__(self):
print('Creating Spam')

如果要进一步定制类,在元类中实现__new__方法,不过通常情况下__init__方法就足够了

描述符的元类

上面类装饰器实现的内容,也可以使用元类来实现,也就是LineItem这个类实例化的时候就会执行元类的init方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Entity是一个元类的实例
class LineItem(Entity):
description = NonBlank()
weight = Quantity()
def __init__(self, description, weight):
self.description = description
self.weight = weight
def subtotal(self):
return self.weight * self.price
# 定义一个元类,然后创建一个元类的实例
class EntityMeta(type):
def __init__(cls, name, bases, attr_dict):
super().__init__(name, bases, attr_dict)
for key, attr in attr_dict.items():
if isinstance(attr, Validated):
type_name = type(attr).__name__
attr.storage_name = '_{}#{}'.format(type_name, key)
# 定义元类的实例
class Entity(metaclass=EntityMeta):

元类的特殊方法__prepare__

__prepare__特殊方法只在元类中有用,必须声明为类方法,在调用元类的__new__方法之前会先调用__prepare__方法,使用类定义体中的属性创建映射。元类构建新类时候,__prepare__方法返回的映射会传给__new__方法最后一个参数,然后传给__init__方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 定义一个元类,然后创建一个元类的实例
class EntityMeta(type):
@classmethod
# 第一个参数是元类,要构建的类的名称和基类组成的元组
def __prepare__(cls, name, bases):
return collections.OrderedDict()
def __init__(cls, name, bases, attr_dict):
super().__init__(name, bases, attr_dict)
cls._field_names = []
for key, attr in attr_dict.items():
if isinstance(attr, Validated):
type_name = type(attr).__name__
attr.storage_name = '_{}#{}'.format(type_name, key)
cls._field_names.append(key)
# 定义元类的实例
class Entity(metaclass=EntityMeta):
@classmethod
def field_names(cls):
for name in cls._field_names:
yield name