类元编程:
在运行时创建或定制类的技艺。在Python中,类是一等对象,因此任何时候都可以使用函数新建类。而无需使用class关键字。
类装饰器也是函数,不过能审查、修改,甚至把被装饰的类替换成其他类。
利用工厂函数生成类
类工厂函数:
collections.nametuple。我们把一个类名和几个属性名传给这个函数,它会创建一个tuple的子类,其中的元素通过名称获取,还为调式提供了友好的字符串表示形式(__repr__)
自定义一个类工厂函数:
def record_factory(cls_name, field_name): try: field_name = field_name.replace(',',' ').split() except AttributeError: pass field_name = tuple(field_name) def __init__(self,*args,**kwargs): attr = dict(zip(self.__slots__, args)) attr.update(kwargs) for key,value in attr.items(): setattr(self, key, 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)) temp = '{}({})'.format(self.__class__.__name__,values) return temp cls_attrs = dict(__slots__ = field_name, __init__ = __init__, __iter__ = __iter__, __repr__ = __repr__) return type(cls_name, (object,), cls_attrs) Dog = record_factory('Dog','name age owner') rex = Dog('Rex' , 39 , 'DDD') print(rex.name)
(1)我们把type视作函数,因为我们像函数那样使用它,调用type(my_object)获取对象所属的类,作用与my_object.__class__相同;然而,type是一个类。当成类使用时,传入三个参数可以新建一个类。
MyClass = type(‘MyClass’, (MySuperClass, MyMixin), {'x':42, 'x2':lambda self:self.x *2})
type的三的参数分别是name、bases和dict,最后一个参数是一个映射,指定新类的属性名和值。等效于
class MyClass(MySuperClass, MyMixin): x = 42 def x2(self): return self.x * 2
特别的,type的实例是类。
__slots__属性的主要特色是节省内存,能处理数百万个实例,不过也有一些缺点。
把三个参数传给type是动态创建类的常用方式。
collections.nametuple函数有另一种方式:先声明一个_class_template变量,其值是字符串形式的源码模板,然后再namedtuple函数中调用_class_templete.format(...)方法,填充模板里的空白,
最后,使用内置的exec函数计算得到的源码字符串。
▲ record_factory函数创建的类,其实例有个局限:不能序列化,即不能使用pickle模块里的dump/load函数处理。
定制描述符的类装饰器
定制描述符的类装饰器:
起因:我们不能使用描述性的存储属性名称,因为实例化描述符时,无法得知托管属性的名称。(即绑定到描述符上的类属性)
可是,一旦组建好整个类,而且把描述符绑定到类属性上之后,我们就可以审查类,并为描述符设置合理的存储属性名称。
可是,一旦LineItem类构建好了,描述符与托管属性之间的绑定就不会变了。因此,我们要在创建类时设置存储属性的名称。
使用类装饰器或元类可以做到这一点。
def entity(cls): for key, attr in cls.__dict__.items(): if isinstance(attr,Validated): attr.storage_name = '_{}#{}'.format(type(attr).__name__, key) return cls @entity class LineItem: description = NonBlank() weight = Quantity() price = Quantity() def __init__(self, description, weight, price): self.description = description self.weight = weight self.price = price
(1)类装饰器能以较简单的方式做到以前需要使用元类去做的事情--创建类时定制类。
(2)类装饰器有个重大缺点:只对直接依附的类有效。
(3)被装饰的类的子类可能继承也可能不继承装饰器所做的改动。
导入时和运行时
导入时和运行时比较:
导入时,Python解释器从上到下一次性解析完.py模块的源码,然后生成用于执行的字节码。如果句法有错误,就在此时报告。
如果本地__pycache__文件夹中有最新的.pyc文件,解释器会跳过上述步骤,因为已经有运行所需的字节码了。
编译肯定是导入时的活动,不过那个时期还会做其他事,因为Python中的语句几乎都是可执行的,也就是说语句可能会运行用户代码,修改用户程序的状态。
尤其是import语句。它不只是声明,在进程中首次导入模块时,还会运行所导入模块中的全部顶层代码。
以后导入相同的模块则使用缓存,只做名称绑定。那些顶层代码可以做任何事情,包括通常在“运行时”做的事,(连接数据库)
导入时和运行时界线是模糊的,import语句可以触发任何“运行时”的行为。
解释器会编译函数的定义体(首次导入模块时),把函数对象绑定到对应的全局名称上,但是显然解释器不会执行函数的定义体。
通常这意味着解释器在导入时定义顶层函数,但是仅当在运行时调用函数时才会执行函数的定义体。
对类来说,在导入时,解释器会执行每个类的定义体,甚至会执行嵌套类的定义体。执行类定义体的结果是,定义了类的属性和方法,并构建了类对象。
类的定义体属于“顶层代码”,因为他在导入时运行。
计算时间的练习:
场景1:>>> import evaltime
场景2:>>> python3 evaltime.py
from evalsupport import deco_alpha print('<[1]> evaltime module start') class ClassOne(): print('<[2]> ClassOne body') def __init__(self): print('<[3]> ClassOne.__init__') def __del__(self): print('<[4]> ClassOne.__del__') def method_x(self): print('<[5]> ClassOne.method_x') class ClassTwo(object): print('<[6]> ClassTwo body') @deco_alpha class ClassThree(): print('<[7]> ClassThree body') def method_y(self): print('<[8]> ClassThree.method_y') class ClassFour(ClassThree): print('<[9]> ClassFour body') 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')
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')
场景1运行结果:
>>> import evaltime <[100]> evalsupport module start <[400]> MetaAleph body # 说明类的 body 在导入时就执行 <[700]> evalsupport module end <[1]> evaltime module start <[2]> ClassOne body <[6]> ClassTwo body # 类的 body 中有其他类, 也会被执行 <[7]> ClassThree body # 先执行类的body, 然后将类作为类装饰器的参数 <[200]> deco_alpha <[9]> ClassFour body # 类继承并不会继承类装饰器 <[14]> evaltime module end
场景2运行结果:
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]> deco_alpha:inner_1 # ClassFour 继承的是经由装饰器修改后的ClassThree <[14]> evaltime module end <[4]> ClassOne.__del__ # 退出时垃圾回收
总结:
(1)解释器会执行所导入模块及其依赖中的每个类定义体。
(2)解释器先计算类的定义体,然后调用依附在类上的装饰器函数,先构建类对象,装饰器才有类对象可处理。
(3)类装饰器对子类没有影响,除非子类使用了super()语句。
元类基础知识
元类基础知识:
元类是制造类的工厂,不过不是函数,而是类。
根据Python对象模型,类是对象,因此类肯定是另外某个类的实例。
默认情况下,Python中的类是type类的实例。也就是说,type是大多数内置的类和用户定义的类的元类;
为了避免无限回溯,type是其自身的实例。(object是type的实例,而type是object的子类)
除了type,标准库中还有一些别的元类,如:ABCMeta和Enum。
元类从type类继承了构建类的能力。(所有类都是type的实例,但是元类还是type的子类。)
元类可以通过实现__init__方法定制实例。元类的__init__方法可以做到类装饰器能做到的任何事情,但是作用更大。
元类计算时间的练习:
场景3:>>> import evaltime_meta
场景4:>>> python3 evaltime_meta.py
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')
场景3运行结果:
>>> 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__ # 先 ClassFour 定义, 然后交给其元类对他加工 <[9]> ClassSix body <[500]> MetaAleph.__init__ # 先 ClassFour 定义, 然后交给其元类对他加工, 由于继承自 ClassFive, 其元类同上(注意区分基类与元类) <[15]> evaltime_meta module end
场景4运行结果:
python3 evaltime_meta.py <[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__ <[11]> ClassThree tests .............................. <[300]> deco_alpha:inner_1 <[12]> ClassFour tests .............................. <[5]> ClassFour.method_y <[13]> ClassFive tests .............................. <[7]> ClassFive.__init__ <[600]> MetaAleph.__init__:inner_2 <[14]> ClassSix tests .............................. <[7]> ClassFive.__init__ <[600]> MetaAleph.__init__:inner_2 <[15]> evaltime_meta module end
总结:
(1)创建ClassFive时调用了MetaAleph.__init__方法。
(2)创建ClassFive的子类ClassSix时也调用了MetaAleph.__init__方法。
(3)__init__方法,四个参数:self(或者写成 cls):要初始化的类对象,(如ClassFive),name、bases、dic与构建类时传给type的参数一样。
(4)先执行类的定义体,再去执行元类的__init__方法。
注意:
ClassSix类没有直接引用MetaAleph类,但是却受到了影响,因为它是ClassFive的子类,进而也是MetaAleph类的实例,所以由MetaAleph.__init__方法初始化。
class FooMeta(type): print('FooMeta__class__') def __init__(cls,name,bases,dic): print('FooMeta.__init__') class Foo(metaclass=FooMeta): print('Foo__class__') def __init__(self): print('Foo.__init__') FooMeta__class__ Foo__class__ FooMeta.__init__
利用元类定制描述符
定制描述符的元类:
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): attr.storage_name = '_{}#{}'.format(type(attr).__name__, key) class Entity(metaclass=EntityMeta): """""" class LineItem(Entity): description = NonBlank() weight = Quantity() price = Quantity() def __init__(self, description, weight, price): self.description = description self.weight = weight self.price = price
元类的特殊方法__prepare__:
元类或者类装饰器获得映射时,属性在类定义体中的顺序已经丢失。
特殊方法__prepare__只在元类中有用,而且必须声明为类方法(用@classmethod 装饰器定义)。
解释器调用元类的__new__方法前,会先调用__prepare__方法,使用类定义体中的属性创建映射。
__prepare__方法的第一个参数是元类,随后两个参数分别是要构建的类的名称和基类组成的元组。返回值必须是映射。
元类构建新类时,__prepare__方法返回的映射会传给__new__方法的最后一个参数,然后再传给__init__方法。
class EntityMeta(type): @classmethod def __prepare__(metacls, name, bases): return collections.OrderedDict() def __init__(self, cls, bases, attr_dict): super().__init__(cls, bases, attr_dict) cls._field_names = [] for key, attr in attr_dict.items(): if isinstance(attr, Validated): attr.storage_name = '_{}#{}'.format(type(attr).__name__, key) cls._field_names.append(key) class Entity(metaclass=EntityMeta): """带有验证字段的业务实体""" @classmethod def field_names(cls): for key in cls._field_names: yield key
类作为对象:
__mor__、__class__、__name__
cls.__bases__;由类的基类组成的元组
cls.__qualname__;类或函数的限定名称,
cls.__subclasses__();返回列表,包含类的直接子类,是内存里现存的子类。
cls.mro();构建类时,如果需要获取存储在类属性__mro__中的超类元组,解释器会调用这个方法。