# 第21章 类元编程
"""
类元编程指的是运行时创建或定制类的技艺
1.类是一等对象,任何时候都可以使用函数新建类,而无需使用class关键字
2.类装饰器也是函数,不过能够审查/修改/甚至把被装饰的类替换成其他类
3.元类功能强大,但难以掌握,类装饰器能用更简单的方式解决很多问题
"""
# 21.1 类工厂函数
# collections.namedtuple:
# 把一个类名和几个属性名传给这个函数,它会创建一个tuple的子类,
# 其中的元素通过名称获取,还为调式提供了友好的字符串表示形式(__repr__)
# 使用类似于collections.namedtuple工厂函数的方式,建立一个类工厂函数
# 21-2 record_factory.py:一个简单的类工厂函数
# type:
"""
通常,我们把type()视为函数,调用type(my_object)获取对象所属的类
效果和my_object.__class__一样,
然而type是一个类,当作类使用时传入三个参数可以新建一个类:
MyClass = type('MyClass',(MySuperClass,MyMixin),
{'x':42,'x2':lambda self :self.x*2})
三个参数分别是 name/base/dict
dict是映射,指定新类的属性和值
上述代码等价于
class MyClass(MySuperClass,MyMixin):
x = 42
def x2(slef):
return self.x *2
type的实例是类,type是自身的实例
把三个参数传给type是动态创建类的常用方式
collctions.namedtuple使用的是另一种方式:
先申明一个_class_template变量,其值是字符串形式的源码模版,
然后namedtuple函数调用_class_template的format方法,填充模版里的空白
最后使用内置的exec函数计算得到的源码字符串
"""
# 21.2 定制描述符的类装饰器
"""
类装饰器和函数装饰非常类似,是参数为类对象的函数,返回原来的类或修改后的类"""
# 示例 21-3 bulkfood_v6.py:使用Quantity和NonBlank描述符的LineItem类
# 示例 21-4 model_v6.py :一个简单的类装饰器
"""
类装饰器有个重大缺陷:
只对直接依附的类有效,被装饰的类的子类可能继承也可能不会继承装饰器所做的改动
具体情况视改动的方式而定
"""
# 21.3导入时和运行时的比较
"""
导入时和运行时:
python解释器会从上到下一次性解析完.py的源码,然后生成用于执行的字节码
如果句法有错误,就在此时报告
如果本地的__pycache__文件夹中有最新的.pyc文件,则跳过上述步骤,因为已经有运行所需的字节码了
编译肯定是导入时的活动,不过那个时期还会做其他事情,
因为python语句几乎都是可执行的,也就是说语句可能会运行用户代码,修改用户程序状态,
尤其是import语句,它不只是申明
在用户首次导入模块时,还会运行模块中的全部顶层代码--以后带入相同的模块则使用缓存,只做名称绑定.
哪些顶层代码可以做任何事情,包括通常在运行时做的事情,比如连接数据库
对于模块中的函数.运行顶层代码时,会编译函数的定义体,但并不会执行,仅当在运行时调用函数才执行
对于类来说,情况就不同了
在导入时,解释器会执行每个类的定义体,甚至会执行嵌套类的定义体
结果是定义了类的属性和方法,并构建了类对象
"""
# 理解计算时间的练习
# 示例21-6 evaltime.py 按顺序写出输出的序号标记<[N]>
# 21.4 元类的基础知识
"""
元类是制造类的工厂,不过不是函数而是类
根据Python对象模型,类是对象,因此类肯定是另外某个类的实例
默认情况下,python中的类是type类的实例,即
type类是大多数内置的类和用户自定义的类的元类
object和type的关系:
object是type的实例,
type是object的子类
所有的类都直接或者间接地是type的实例,不过只有元类同时也是type的子类
"""
print('spam'.__class__) # <class 'str'>
print(str.__class__) # <class 'type'>
print(type.__class__) # <class 'type'> type是其自身的实例
# 其他元类 abc.ABCMeta
import collections
print(collections.Iterable.__class__) # <class 'abc.ABCMeta'>
import abc
print(abc.ABCMeta.__class__) # <class 'type'>
print(abc.ABCMeta.__mro__)
"""
元类可以通过实现__init__方法定制实例,元类的__init__方法可以做到
类装饰器能做的任何事情,但是作用更大"""
# 理解元类计算时间的练习
# 示例21-10 evaltime_meta.py :ClassFive是MetaAleph元类的实例
# 21.5 定制描述符的元类
# 示例 21-14 bulkfood_v7.py : 有元类支持,继承model.Entity类即可
# 示例 21-15 model_v7.py:EntityMeta元类以及它的一个实例
# 21.6元类的特殊方法__prepare__
# 示例21-16 model_v8.py:这一版的EntityMeta用到了__prepare__方法,而且为Entity定义了field_names类方法
"""
元类的作用:
1.验证属性
2.一次把装饰器依附到多个方法上
3.序列化对象或者转换数据
4.对象关系映射
5.基于对象的持久存储
6.动态转换使用其他语言编写的类结构
"""
#21.7 类作为对象
"""
python数据模型为每个类定义了很多属性
__mro__
__class__
__name__
cls.__bases:由类的基类组成的元组
cls.__qualname__:其值是类或者函数的限定名称,即从模块的全局作用域到类的点分路径
cls.__subclasses__():这个方法返回一个列表,包含类的直接子类,这个方法的实现使用弱引用
cls.mro():构建类时,如果需要获取存储在类属性__mro__中的超类元组,解释器会调用这个方法,定制
要构建类的解析方法顺序"""
# 21-2 record_factory.py:一个简单的类工厂函数
# 21-2 record_factory.py:一个简单的类工厂函数
def record_factory(cls_name,field_names):
try:
field_names = field_names.replace(',',' ').split() # 1
except AttributeError: # 不能调用replace或者split方法
pass # 假定field_names本就是标识符组成的序列
field_names = tuple(field_names) # 2
def __init__(self,*args,**kwargs): # 3
attrs = dict(zip(self.__slots__,args))
attrs.update(kwargs)
for name,value in attrs.items():
setattr(self,name,value)
def __iter__(self): # 4
for name in self.__slots__:
yield getattr(self,name)
def __repr__(self): # 5
values = ', '.join('{}={!r}'.format(*i)
for i in zip(self.__slots__,self))
return '{}({})'.format(self.__class__.__name__,values)
cls_attrs = dict(__slots__ = field_names, # 6
__init__ = __init__,
__iter__ = __iter__,
__repr__ = __repr__)
return type(cls_name,(object,),cls_attrs) # 7
"""
说明:
1.这里体现了鸭子类型:尝试在逗号或者空格拆分field_names,如果失败,那么假定field_names
本就是可迭代的对象,一个元素对应一个属性名
2.使用属性名构建元组,浙江成为新建类的__slots__属性;此外这个么做,还设定了拆包的顺序和字符串
表示形式中各字段的顺序.
3.这个函数将成为新建类的__init__方法,参数有位置参数或关键字参数
4.实现了__iter__方法,把类的实例变成可迭代的对象;按照__slots__的顺序产出字段值
5.迭代__slots__和self,生成友好的字符串表示形式
6.组建类属性字典
7.调用type构造方法,构建新类,返回将其返回
"""
if __name__ == '__main__':
Dog = record_factory('Dog','name weight owner')
rex = Dog('REX',30,'Bob','color'=='white') # 可以这样写,但是不能把属性加入实例的属性
# print(rex.color) # 报错
print(repr(rex))
# print(rex.__slots__)
# print(Dog.__dict__)
name,weight,_ = rex # 实例是可迭代的对象
print(name,weight)
print("{2}'s dog weight {1}kg".format(*rex)) # 也可以拆包
rex.weight = 32 # 实例是可变的对象
print(repr(rex))
print(Dog.__mro__) # 新类继承自object和工厂函数没有关系
# 示例 21-3 bulkfood_v6.py:使用Quantity和NonBlank描述符的LineItem类
# 示例 21-3 bulkfood_v6.py:使用Quantity和NonBlank描述符的LineItem类
import model_v6 as model
@model.entity #添加了类装饰器
class LineItem:
weight = model.Quantity()
price = model.Quantity()
description = model.NonBlank()
def __init__(self,description,weight,price):
self.description = description
self.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price
if __name__ == '__main__':
raisins = LineItem('Golden raisins',10,6.95)
print(dir(raisins)) # '_NonBlank#description', '_Quantity#price', '_Quantity#weight'
print(LineItem.description.storage_name) #_NonBlank#description
print(raisins.description)
print(getattr(raisins, '_NonBlank#description'))
# 示例 21-4 model_v6.py :一个简单的类装饰器
# 示例 21-4 model_v6.py :一个简单的类装饰器
import abc
class AutoStorage: #1 AutoStorage提供了之前Quantity的大部分功能
__counter = 0
def __init__(self):
cls = self.__class__
prefix = cls.__name__
index = cls.__counter
self.storage_name = '_{}#{}'.format(prefix,index)
cls.__counter += 1
def __get__(self,instance,ower):
# ower是托管类的引用
if instance is None:
return self # 如果不是通过实例调用,返回描述符自身
else: # 否则像之前一样返回托管属性的值
return getattr(instance,self.storage_name)
def __set__(self, instance, value):
setattr(instance,self.storage_name,value) #2 ..验证部分除外
class Validated(abc.ABC,AutoStorage): # 3 抽象类也继承AutoStorage
def __set__(self, instance, value):
value = self.validate(instance,value) # 4 把验证操作委托给validate方法
super().__set__(instance,value) # 5 然后把返回的值传给超类的__set__方法
@abc.abstractmethod
def validate(selfs,instance,value): # 6 validate是抽象方法
'''return validated value or raise ValueError'''
class Quantity(Validated): # 7 Quantity和NonBlank都继承自Validated
'''a number greater than zero'''
def validate(selfs,instance,value):
if value <= 0:
raise ValueError('value must be > 0 ')
return value
class NonBlank(Validated):
'''a string with at least one non-space character'''
def validate(selfs,instance,value):
value = value.strip()
if len(value) == 0:
raise ValueError('value cannot be empty or blank')
return value # 8 要求具体的方法返回验证后的值,借机可以清理\转换和规范化接收的数据
"""===============以上为modle_v5.py的代码=================="""
def entity(cls): # 1
for key,attr in cls.__dict__.items(): # 2
if isinstance(attr,Validated): # 3
type_name = type(attr).__name__
attr.storage_name = '_{}#{}'.format(type_name,key) # 4
return cls # 5
"""
说明:
1.装饰器的参数是一个类
2.迭代存储类属性的字典
3.如果属性是Validated描述符实例
4.使用买描述符类的托管属性的名称命名storage_name(例如_NonBlank#description)
5.返回修改后的类
"""
# 示例21-6 evaltime.py 按顺序写出输出的序号标记<[N]>
from evalsupport import deco_alpha
print('<[1]> evaltime module start ')
class ClassOne:
print('<[2]> CalssOne body ')
def __init__(self):
print('<[3]> CalssOne.__init__')
def __del__(self):
print('<[4]> CalssOne.__del__')
def method_x(self):
print('<[5]> CalssOne.method_x')
class ClassTwo(object):
print('<[6]> CalssTwo body ')
@deco_alpha
class ClassThree():
print('<[7]> ClassThree body ')
def method_y(self):
print('<[8]> CalssThree.method_y')
class ClassFour(ClassThree):
print('<[9]> ClassFour body ')
def method_y(self):
print('<[10]> CalssFour.method_y')
if __name__ == '__main__':
print('<[11]> ClassOne tests',30*".")
one = ClassOne()
one.method_x()
print('<[12]> ClassThree tests', 30 * ".")
three = ClassThree()
three.method_y()
print('<[13]> ClassFour tests', 30 * ".")
four = ClassFour()
four.method_y()
print('<[14]> evaltime module end')
evalsupport.py
print('<[100]> evalsuport 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]> evalsuport module end')
# 示例21-10 evaltime_meta.py :ClassFive是MetaAleph元类的实例
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]> CalssThree.method_y')
class ClassFour(ClassThree):
print('<[4]> ClassFour body ')
def method_y(self):
print('<[5]> CalssFour.method_y')
class ClassFive(metaclass=MetaAleph):
print('<[6]> ClassFive body ')
def method_y(self):
print('<[7]> CalssFive.method_y')
def method_z(self):
print('<[8]> CalssFive.method_z')
class ClassSix(ClassFive):
print('<[9]> ClassSix body ')
def method_z(self):
print('<[10]> CalssSix.method_z')
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')
# 示例 21-14 bulkfood_v7.py : 有元类支持,继承model.Entity类即可
# 示例 21-14 bulkfood_v7.py : 有元类支持,继承model.Entity类即可
# import model_v7 as model
import model_v8 as model
class LineItem(model.Entity): # LineItem是model.Entity的子类
weight = model.Quantity()
price = model.Quantity()
description = model.NonBlank()
def __init__(self,description,weight,price):
self.description = description
self.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price
if __name__ == '__main__':
raisins = LineItem('Golden raisins',10,6.95)
print(dir(raisins)) # '_NonBlank#description', '_Quantity#price', '_Quantity#weight'
print(LineItem.description.storage_name) #_NonBlank#description
print(raisins.description)
print(getattr(raisins, '_NonBlank#description'))
# model_v8的新功能
for name in LineItem.field_names():
print(name)
# 示例 21-15 model_v7.py:EntityMeta元类以及它的一个实例
# 示例 21-15 model_v7.py:EntityMeta元类以及它的一个实例
import abc
class AutoStorage: #1 AutoStorage提供了之前Quantity的大部分功能
__counter = 0
def __init__(self):
cls = self.__class__
prefix = cls.__name__
index = cls.__counter
self.storage_name = '_{}#{}'.format(prefix,index)
cls.__counter += 1
def __get__(self,instance,ower):
# ower是托管类的引用
if instance is None:
return self # 如果不是通过实例调用,返回描述符自身
else: # 否则像之前一样返回托管属性的值
return getattr(instance,self.storage_name)
def __set__(self, instance, value):
setattr(instance,self.storage_name,value) #2 ..验证部分除外
class Validated(abc.ABC,AutoStorage): # 3 抽象类也继承AutoStorage
def __set__(self, instance, value):
value = self.validate(instance,value) # 4 把验证操作委托给validate方法
super().__set__(instance,value) # 5 然后把返回的值传给超类的__set__方法
@abc.abstractmethod
def validate(selfs,instance,value): # 6 validate是抽象方法
'''return validated value or raise ValueError'''
class Quantity(Validated): # 7 Quantity和NonBlank都继承自Validated
'''a number greater than zero'''
def validate(selfs,instance,value):
if value <= 0:
raise ValueError('value must be > 0 ')
return value
class NonBlank(Validated):
'''a string with at least one non-space character'''
def validate(selfs,instance,value):
value = value.strip()
if len(value) == 0:
raise ValueError('value cannot be empty or blank')
return value # 8 要求具体的方法返回验证后的值,借机可以清理\转换和规范化接收的数据
"""===============以上为modle_v5.py的代码=================="""
class EntityMeta(type):
'''元类,用于创建带验证字段的业务实体'''
def __init__(cls,name,bases,attr_dict):
super().__init__(name,bases,attr_dict) # 1 在超类type上调用__init__方法
for key,attr in attr_dict.items(): # 2 与类装饰器的逻辑一样
if isinstance(attr,Validated):
type_name = type(attr).__name__
attr.storage_name = '_{}#{}'.format(type_name,key)
class Entity(metaclass=EntityMeta): # 这个类的存在只是为了用起来便利,这个模块的用户直接继承Entity即可
'''带有验证字段的实体业务'''
# 示例21-16 model_v8.py:这一版的EntityMeta用到了__prepare__方法,而且为Entity定义了field_names类方法
# 示例21-16 model_v8.py:这一版的EntityMeta用到了__prepare__方法,而且为Entity定义了field_names类方法
# 示例 21-15 model_v7.py:EntityMeta元类以及它的一个实例
import abc
import collections
class AutoStorage: #1 AutoStorage提供了之前Quantity的大部分功能
__counter = 0
def __init__(self):
cls = self.__class__
prefix = cls.__name__
index = cls.__counter
self.storage_name = '_{}#{}'.format(prefix,index)
cls.__counter += 1
def __get__(self,instance,ower):
# ower是托管类的引用
if instance is None:
return self # 如果不是通过实例调用,返回描述符自身
else: # 否则像之前一样返回托管属性的值
return getattr(instance,self.storage_name)
def __set__(self, instance, value):
setattr(instance,self.storage_name,value) #2 ..验证部分除外
class Validated(abc.ABC,AutoStorage): # 3 抽象类也继承AutoStorage
def __set__(self, instance, value):
value = self.validate(instance,value) # 4 把验证操作委托给validate方法
super().__set__(instance,value) # 5 然后把返回的值传给超类的__set__方法
@abc.abstractmethod
def validate(selfs,instance,value): # 6 validate是抽象方法
'''return validated value or raise ValueError'''
class Quantity(Validated): # 7 Quantity和NonBlank都继承自Validated
'''a number greater than zero'''
def validate(selfs,instance,value):
if value <= 0:
raise ValueError('value must be > 0 ')
return value
class NonBlank(Validated):
'''a string with at least one non-space character'''
def validate(selfs,instance,value):
value = value.strip()
if len(value) == 0:
raise ValueError('value cannot be empty or blank')
return value # 8 要求具体的方法返回验证后的值,借机可以清理\转换和规范化接收的数据
"""===============以上为modle_v5.py的代码=================="""
class EntityMeta(type):
'''元类,用于创建带验证字段的业务实体'''
@classmethod
def __prepare__(cls, name, bases):
return collections.OrderedDict() # 返回一个空的OrderedDict实例,类属性将存储在里面
def __init__(cls,name,bases,attr_dict):
super().__init__(name,bases,attr_dict)
cls._field_names = [] # 在要构建的类中创建一个_field_names属性
for key,attr in attr_dict.items(): # 这里的 attr_dict是一个OrderedDict对象,会按顺序迭代
if isinstance(attr,Validated):
type_name = type(attr).__name__
attr.storage_name = '_{}#{}'.format(type_name,key)
cls._field_names.append(key) # 把找到的各个Validated字段添加到_field_names中
class Entity(metaclass=EntityMeta): # 这个类的存在只是为了用起来便利,这个模块的用户直接继承Entity即可
'''带有验证字段的实体业务'''
@classmethod
def field_names(cls):
for name in cls._field_names:
yield name # 按照添加字段的顺序产出字段的名称
35岁学python,也不知道为了啥?