流畅的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(...)函数不会上面所提到的任何一个属性。