类元编程

类元编程:

  在运行时创建或定制类的技艺。在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')
evaltime.py
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')
evalsupport.py

  场景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')
evaltime_meta.py

场景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__中的超类元组,解释器会调用这个方法。

原文地址:https://www.cnblogs.com/5poi/p/11478623.html