• 类元编程


    类元编程:

      在运行时创建或定制类的技艺。在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__中的超类元组,解释器会调用这个方法。

  • 相关阅读:
    Oracle 11g安装过程工作Oracle数据库安装图解
    Anaconda和虚拟环境
    NLP(三)
    Spring Cloud
    Flink 更新中
    Hadoop(四)小项目练习 更新中
    大数据环境搭建
    zookeeper
    .net(四) winform应用程序
    负载均衡中间件(二)LVS负载均衡软件和基于云计算平台的架构
  • 原文地址:https://www.cnblogs.com/5poi/p/11478623.html
Copyright © 2020-2023  润新知