流畅的python,Fluent Python 第二十一章笔记 (类元编程)
首先上一个类工厂函数:
def record_factory(cls_name, field_names):
try:
field_names = field_names.replace(',', ' ').split()
except AttributeError:
...
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,其中的zip参数里面的self会调用__iter__
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)
if __name__ == '__main__':
...
这是通过函数返回一个类的类工厂函数。下面展示执行效果。
>>> Dog = record_factory('Dog','name weight owner')
>>> rex = Dog('rex', 30, 'sidian')
>>> for i in rex:
... print(i)
...
rex
30
sidian
>>> rex
Dog(name='rex', weight=30, owner='sidian')
>>> Dog.__mro__
(<class '__main__.Dog'>, <class 'object'>)
>>>
执行中,报错了4次,丢人了,抄写个代码错了4次。
我们可以把type当做函数,因为我们可以像函数那样使用它
type(类名, 继承的父类(元祖形式), 一个映射(指定的新类的属性名和值))
MyClass = type('MyClass', (object,), {'x': 42, 'x2': lambda self: self.x *2})
class MyClass:
x = 42
def x2(self):
return self.x * 2
这里写了两个类的定义其实是一样的,有写书中说def, class只不过是Python的语法糖
定义描述符的装饰器
上一节最后一个任务就是给描述符属性定义了一个独特的名字,用于给托管实例添加属性。
但那样的显示效果比较糟糕,我们不能使用存储属性名称,因为在实例化描述符时无法得知托管属性。
书中的说明比较抽象,我的理解就时在类完成后,没有执行__init__初始化之前,调整描述符实例既托管类属性的初始化值。
数中说使用__new__方法中无用,可能是翻译的问题,但我觉的__new__中也能实现,逻辑也时一样的。
先按照书中通过装饰完成。
def entity(cls):
# 遍历类属性
for key, attr in cls.__dict__.items():
# 如果是Validate基类的属性
if isinstance(attr, Validate):
# type_name是实例类的名字
type_name = type(attr).__name__
# print(type_name)
# key为描述符类实例的类属性赋值变量名
attr.storage_name = '_{}#{}'.format(type_name, key)
# print(attr.storage_name)
return cls
import model_v6 as model
from model_v6 import Validate
# @model.entity
class LineItem:
# 描述符实例赋值给托管类属性
weight = model.Quantity()
price = model.Quantity()
description = model.NonBlack()
def __new__(cls, *args, **kwargs):
for key, attr in cls.__dict__.items():
if isinstance(attr, Validate):
type_name = type(attr).__name__
attr.storage_name = '_{}#{}'.format(type_name, key)
return super().__new__(cls)
def __init__(self, description, weight, price):
# 储存实例,托管实例中存储自身托管属性的属性 self.weight与self.price
self.description = description
# 这个按照书中的说法叫特性的赋值方法了,不是属性赋值了。
self.weight = weight
self.price = price
def subtoall(self):
return self.weight * self.price
if __name__ == '__main__':
line = LineItem('ok', 16, 17)
print(vars(line))
print(dir(line))
# print(LineItem.description.storage_name)
/usr/local/bin/python3.7 /Users/shijianzhong/study/Fluent_Python/第二十一章/bulkfood_v6.py
{'_NonBlack#description': 'ok', '_Quantity#weight': 16, '_Quantity#price': 17}
['_NonBlack#description', '_Quantity#price', '_Quantity#weight', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'description', 'price', 'subtoall', 'weight']
Process finished with exit code 0
两种方式执行的效果时一样的,原来的描述符初始化的属性被手动的覆盖了。
导入时和运行时比较。
书中用了两个案例进行了比较。
# 这个被导入肯定执行
print('<[100]> evalsupport module start')
# 感觉时一个类装饰器
def deco_alpha(cls):
print('<[200]> deco_alpha')
def inner_1(self):
print('<[300]> deco_alpha:inner_1')
cls.method_y = inner_1
return cls
# 初始化一个类的类
class MetaAleph(type):
# 类里面的函数都执行了
print('<[400]> MetaAleph body')
def __init__(cls, name, bases, dic):
print('<[500]> MetaAleph.__init__')
def inner_2(self):
print('<[600]> MetaAleph.__init__:inner_2')
cls.method_z = inner_2
print('<[700]> evalsupport module end')
import abc
# 创建一个自动管理和存储属性的描述符类
class AutoStorage:
__count = 0
def __init__(self):
# 描述符初始化赋值
cls = self.__class__
prefil = cls.__name__
index = cls.__count
# 设立一个独一无二的名称
self.storage_name = '_{}#{}'.format(prefil, index)
cls.__count += 1
def __get__(self, instance, owner):
if instance is None:
return self
else:
return getattr(instance, self.storage_name)
def __set__(self, instance, value):
setattr(instance, self.storage_name, value)
# 扩展AutoStorage类的抽象子类,覆盖__set__方法,调用必须由子类事项的validate
class Validate(abc.ABC, AutoStorage):
def __set__(self, instance, value):
value = self.validate(instance, value)
super().__set__(instance, value)
@abc.abstractmethod
def validate(self, instance, value):
'''return validate value or raise ValueError'''
# 给数字不能小于0的属性用的描述符
class Quantity(Validate):
def validate(self, instance, value):
if value < 0 :
raise ValueError('value must be > 0')
return value
# 给字段参数不能为0的属性用描述符
class NonBlack(Validate):
def validate(self, instance, value):
if len(value) == 0:
raise ValueError('value cannot be empty or blank')
return value
def entity(cls):
# 遍历类属性
for key, attr in cls.__dict__.items():
# 如果是Validate基类的属性
if isinstance(attr, Validate):
# type_name是实例类的名字
type_name = type(attr).__name__
# print(type_name)
# key为描述符类实例的类属性赋值变量名
attr.storage_name = '_{}#{}'.format(type_name, key)
# print(attr.storage_name)
return cls
当执行 import evaltime
>>> import evaltime <[100]> evalsupport module start <[400]> MetaAleph body <[700]> evalsupport module end <[1]> evaltime module start <[2]> ClassOne body <[6]> ClassTwo body <[7]> ClassThree body <[200]> deco_alpha <[9]> ClassFour body <[14]> evaltime module end >>>
执行python evaltime.py
<[100]> evalsupport module start <[400]> MetaAleph body <[700]> evalsupport module end <[1]> evaltime module start <[2]> ClassOne body <[6]> ClassTwo body <[7]> ClassThree body <[200]> deco_alpha <[9]> ClassFour body <[11]> ClassOne tests .............................. <[3]> ClassOne.__init__ <[5]> ClassOne.method_x <[12]> ClassThree tests .............................. <[300]> deco_alpha:inner_1 <[13]> ClassFour tests .............................. <[10]> ClassFour.method_y <[14]> evaltime module end <[4]> ClassOne.__del__
从执行结果可以看出来,在导入时,解释器会执行每个类的定义体,甚至会执行嵌套类的定义体。执行类定义体的结果是,定义了类的属性和方法,并构建了类对象。
元类基础知识
元类时生产类的工厂。
python的类和函数都时type类的实例。也可以认为类与函数都是type创建出来的。
object类和type类之间的关系很独立:obejct是type的实例,而type是object的子类。这种关系很神奇,而且type是自身的实例。
In [16]: import collections In [17]: collections.Iterable.__class__ /usr/local/bin/ipython:1: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working #!/usr/local/opt/python/bin/python3.7 Out[17]: abc.ABCMeta In [18]: import abc In [19]: abc.ABCMeta.__class__ Out[19]: type In [20]: abc.ABCMeta.__mro__ Out[20]: (abc.ABCMeta, type, object) In [21]:
从这个运行结果可以看出collections类由abc.ABCMeta创建。
abc.ABCMeta是type的子类,而且也是type的实例。
ABCmeta是元类。
重点就是所有的类都是type的子类,但元类还是type的子类,元类因为继承了type所有可以创建类。
理解元类计算时间的联系
下面是测试代码:
from evalsupport import deco_alpha
from evalsupport import MetaAleph
print('<[1]> evaltime_meta module start')
@deco_alpha
class ClassThree():
print('<[2]> ClassThree body')
def method_y(self):
print('<[3]> ClassThree.method_y')
class ClassFour(ClassThree):
print('<[4]> ClassFour body')
def method_y(self):
print('<[5]> ClassFour.method_y')
# 由元类创建的类
class ClassFive(metaclass=MetaAleph):
print('<[6]> ClassFive body')
def __init__(self):
print('<[7]> ClassFive.__init__')
def method_z(self):
print('<[8]> ClassFive.method_y')
# 继承一个由元类创建的类
class ClassSix(ClassFive):
print('<[9]> ClassSix body')
def method_z(self):
print('<[10]> ClassSix.method_y')
if __name__ == '__main__':
print('<[11]> ClassThree tests', 30 * '.')
three = ClassThree()
three.method_y()
print('<[12]> ClassFour tests', 30 * '.')
four = ClassFour()
four.method_y()
print('<[13]> ClassFive tests', 30 * '.')
five = ClassFive()
five.method_z()
print('<[14]> ClassSix tests', 30 * '.')
six = ClassSix()
six.method_z()
print('<[15]> evaltime_meta module end')
>>> import evaltime_meta <[100]> evalsupport module start <[400]> MetaAleph body <[700]> evalsupport module end <[1]> evaltime_meta module start <[2]> ClassThree body <[200]> deco_alpha <[4]> ClassFour body <[6]> ClassFive body <[500]> MetaAleph.__init__ <[9]> ClassSix body <[500]> MetaAleph.__init__ <[15]> evaltime_meta module end
由结果可以看出,在执行import的时候,那些有元类创建的类,会调用元类的__init__方法,(__new__应该也会调用)
后面继承了该类的类,也会同样调用父类的创建方式,这个是类装饰器做不到的。
在__init__中,能够对已经创建的类进行属性的修改。
shijianzhongdeMacBook-Pro:第二十一章 shijianzhong$ python3 evaltime.py <[100]> evalsupport module start <[400]> MetaAleph body <[700]> evalsupport module end <[1]> evaltime module start <[2]> ClassOne body <[6]> ClassTwo body <[7]> ClassThree body <[200]> deco_alpha <[9]> ClassFour body <[11]> ClassOne tests .............................. <[3]> ClassOne.__init__ <[5]> ClassOne.method_x <[12]> ClassThree tests .............................. <[300]> deco_alpha:inner_1 <[13]> ClassFour tests .............................. <[10]> ClassFour.method_y <[14]> evaltime module end <[4]> ClassOne.__del__
从运行结果来看,通过元类创建的类,经过元类的__init__函数修改,把原来的一个类属性(一个函数)进行了修改。
如果想进一步定义类,可以在元类中实现__new__方法。不过通常下__init__方法就够了。
# 这个被导入肯定执行
print('<[100]> evalsupport module start')
# 感觉时一个类装饰器
def deco_alpha(cls):
print('<[200]> deco_alpha')
def inner_1(self):
print('<[300]> deco_alpha:inner_1')
cls.method_y = inner_1
return cls
# 初始化一个元类
class MetaAleph(type):
# 类里面的函数都执行了
print('<[400]> MetaAleph body')
def __new__(cls, *args, **kwargs):
# print(args, kwargs)
def inner_2(self):
print('<[600]> MetaAleph.__init__:inner_2')
# 通过 __new__里面的参数修改最后一项字典参数里面修改原有的函数属性
args[-1]['method_z'] = inner_2
return super().__new__(cls, *args, **kwargs)
def __init__(cls, name, bases, dic):
# print(name, bases, dic)
print('<[500]> MetaAleph.__init__')
# def inner_2(self):
# print('<[600]> MetaAleph.__init__:inner_2')
#
# cls.method_z = inner_2
print('<[700]> evalsupport module end')
上面是通过__new__实现的,主要是通过修改args,那是需要被创建的类所携带的自身原来的类属性。
定制描述符的元类。
通过元类修改类属性描述符实例的属性。
import abc
# 创建一个自动管理和存储属性的描述符类
class AutoStorage:
__count = 0
def __init__(self):
# 描述符初始化赋值
cls = self.__class__
prefil = cls.__name__
index = cls.__count
# 设立一个独一无二的名称
self.storage_name = '_{}#{}'.format(prefil, index)
cls.__count += 1
def __get__(self, instance, owner):
if instance is None:
return self
else:
return getattr(instance, self.storage_name)
def __set__(self, instance, value):
setattr(instance, self.storage_name, value)
# 扩展AutoStorage类的抽象子类,覆盖__set__方法,调用必须由子类事项的validate
class Validate(abc.ABC, AutoStorage):
def __set__(self, instance, value):
value = self.validate(instance, value)
super().__set__(instance, value)
@abc.abstractmethod
def validate(self, instance, value):
'''return validate value or raise ValueError'''
# 给数字不能小于0的属性用的描述符
class Quantity(Validate):
def validate(self, instance, value):
if value < 0 :
raise ValueError('value must be > 0')
return value
# 给字段参数不能为0的属性用描述符
class NonBlack(Validate):
def validate(self, instance, value):
if len(value) == 0:
raise ValueError('value cannot be empty or blank')
return value
def entity(cls):
# 遍历类属性
for key, attr in cls.__dict__.items():
# 如果是Validate基类的属性
if isinstance(attr, Validate):
# type_name是实例类的名字
type_name = type(attr).__name__
# print(type_name)
# key为描述符类实例的类属性赋值变量名
attr.storage_name = '_{}#{}'.format(type_name, key)
# print(attr.storage_name)
return cls
class EntityMeta(type):
def __init__(cls, name, bases, attr_dict):
# 书中是先调用了超类的__init__方法
# super().__init__(name, bases, attr_dict)
# 创建类以后,修改类用的attr_dict参数,这里面是类属性的字典对照表,修改类属性的参数
# 这个其实是一个猴子布丁,因为类是type的实例,修改type中的一些参数,将直接影响到实例
for key ,attr in attr_dict.items():
if isinstance(attr, Validate):
type_name = attr.__class__.__name__
attr.storage_name = '_{}#{}'.format(type_name, key)
# 但我的理解,这句话写在最后比较更加符合我的理解,因为最后调用最新的参数,初始化创建的类。
super().__init__(name, bases, attr_dict)
# 这一步很骚,用到了Python的抽象作用,一个继承可以让调用者很方便
class Entity(metaclass=EntityMeta):
...
import model_v7 as model
from model_v7 import Validate
# @model.entity
# 通过简单的继承,使用者不必知道内部复杂的逻辑。
class LineItem(model.Entity):
# 描述符实例赋值给托管类属性
weight = model.Quantity()
price = model.Quantity()
description = model.NonBlack()
# def __new__(cls, *args, **kwargs):
# for key, attr in cls.__dict__.items():
# if isinstance(attr, Validate):
# type_name = type(attr).__name__
# attr.storage_name = '_{}#{}'.format(type_name, key)
# return super().__new__(cls)
def __init__(self, description, weight, price):
# 储存实例,托管实例中存储自身托管属性的属性 self.weight与self.price
self.description = description
# 这个按照书中的说法叫特性的赋值方法了,不是属性赋值了。
self.weight = weight
self.price = price
def subtoall(self):
return self.weight * self.price
if __name__ == '__main__':
line = LineItem('ok', 16, 17)
print(vars(line))
print(dir(line))
# print(LineItem.description.storage_name)
元类的特殊方法__perpare__
import abc
# 创建一个自动管理和存储属性的描述符类
class AutoStorage:
__count = 0
def __init__(self):
# 描述符初始化赋值
cls = self.__class__
prefil = cls.__name__
index = cls.__count
# 设立一个独一无二的名称
self.storage_name = '_{}#{}'.format(prefil, index)
cls.__count += 1
def __get__(self, instance, owner):
if instance is None:
return self
else:
return getattr(instance, self.storage_name)
def __set__(self, instance, value):
setattr(instance, self.storage_name, value)
# 扩展AutoStorage类的抽象子类,覆盖__set__方法,调用必须由子类事项的validate
class Validate(abc.ABC, AutoStorage):
def __set__(self, instance, value):
value = self.validate(instance, value)
super().__set__(instance, value)
@abc.abstractmethod
def validate(self, instance, value):
'''return validate value or raise ValueError'''
# 给数字不能小于0的属性用的描述符
class Quantity(Validate):
def validate(self, instance, value):
if value < 0 :
raise ValueError('value must be > 0')
return value
# 给字段参数不能为0的属性用描述符
class NonBlack(Validate):
def validate(self, instance, value):
if len(value) == 0:
raise ValueError('value cannot be empty or blank')
return value
def entity(cls):
# 遍历类属性
for key, attr in cls.__dict__.items():
# 如果是Validate基类的属性
if isinstance(attr, Validate):
# type_name是实例类的名字
type_name = type(attr).__name__
# print(type_name)
# key为描述符类实例的类属性赋值变量名
attr.storage_name = '_{}#{}'.format(type_name, key)
# print(attr.storage_name)
return cls
class EntityMeta(type):
@classmethod
def __prepare__(mcs, name, bases):
import collections
print('__prepare__', name, bases)
# 返回一个空的OrderedDict实例,类属性将存储在里面
return collections.OrderedDict()
def __init__(cls, name, bases, attr_dict):
# 书中是先调用了超类的__init__方法
# super().__init__(name, bases, attr_dict)
# 创建类以后,修改类用的attr_dict参数,这里面是类属性的字典对照表,修改类属性的参数
# 这个其实是一个猴子布丁,因为类是type的实例,修改type中的一些参数,将直接影响到实例
cls._field_name = []
for key ,attr in attr_dict.items():
# 这一行与前期相比没有变化,不过这里的attr_dict是那个OrderedDict对象
# 由解释器在调用__init__方法之前调用__prepare__方法时获得。因此,这个for循环会按照添加属性的顺序迭代属性
if isinstance(attr, Validate):
type_name = attr.__class__.__name__
attr.storage_name = '_{}#{}'.format(type_name, key)
cls._field_name.append(key)
# 但我的理解,这句话写在最后比较更加符合我的理解,因为最后调用最新的参数,初始化创建的类。
super().__init__(name, bases, attr_dict)
# 这一步很骚,用到了Python的抽象作用,一个继承可以让调用者很方便
class Entity(metaclass=EntityMeta):
@classmethod
def field_names(cls):
for name in cls._field_name:
yield name
type构造方法及元类的__new__和__init__方法都会收到要计算的类的定义体,形式是名称到属性的映象。然而在默认情况下,那个映射是字典,也就是说,元类或类装饰器获得映射时,属性在类定义体中中的顺序已经丢失了。
这个问题的解决方法是,使用Python3引入的特殊方案__prepare__。这个特殊方法只在元类中有用,而且必须声明为类方法。
解释器调用元类的__new__方法之前会先调用__prepare__方法,使用类定义体中的属性创建映射。
__prepare__方法的第一个参数是元类,随后两个参数分别是要构建的类的名称和基类组成组成的元祖,返回值必须是映射。元类构建新类时,__prepare__方法返回的映射会传给__new__方案的最后一个参数,然后再传给__init__方法。
元类的作用:
验证属性
一次把装饰器依附到多个方法上
序列化对象或转换数据
对象关系映射
基于对象的持久存储
动态转换使用其他语言编写的类结构
类作为对象
cls.__bases__
由类的基类组成的元祖
cls.__quanname
python3.3新引入的属性,其值是函数或者限定名称,既从模块的全局作用域到类的点分路径。
In [26]: import evaltime In [27]: evaltime.ClassOne.ClassTwo.__qualname__ Out[27]: 'ClassOne.ClassTwo'
cls.__subclass__()
这个方法返回一个列表,包含类的直接子类。这个方法的实现使用弱引用,防止再在超类和子类(子类在__bases__属性中存储指向超类的强引用)之间出现循环引用。
这个方法返回的列表中是内存里现存的子类。
cls.mro()
构建类时,如果需要获取存储再类属性__mro__中的超类元祖,解释器会调用这个方法。
元类可以覆盖这个方法,定制要构建的类解析方法的序列。
dir(...)函数不会上面所提到的任何一个属性。
浙公网安备 33010602011771号