zoukankan      html  css  js  c++  java
  • 类元编程

    类元编程:

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

  • 相关阅读:
    Codeforces467C George and Job
    Codeforces205E Little Elephant and Furik and RubikLittle Elephant and Furik and Rubik
    Codeforce205C Little Elephant and Interval
    51nod1829 函数
    51nod1574 排列转换
    nowcoder35B 小AA的数列
    Codeforce893E Counting Arrays
    gym101612 Consonant Fencity
    CodeForces559C Gerald and Giant Chess
    CodeForces456D A Lot of Games
  • 原文地址:https://www.cnblogs.com/5poi/p/11478623.html
Copyright © 2011-2022 走看看