zoukankan      html  css  js  c++  java
  • 流畅的python,Fluent Python 第二十一章笔记 (类元编程)

    首先上一个类工厂函数:

    def record_factory(cls_name, field_names):
        try:
            field_names = field_names.replace(',', ' ').split()
        except AttributeError:
            ...
        field_names = tuple(field_names)
    
        # 定义初始化函数
        def __init__(self, *args, **kwargs):
            attrs = dict(zip(self.__slots__, args))
            attrs.update(kwargs)
            for name, value in attrs.items():
                setattr(self, name, value)
    
        # 定义成可迭代对象
        def __iter__(self):
            for name in self.__slots__:
                yield getattr(self, name)
    
        # 定义输出
        def __repr__(self):
            # 输出values,其中的zip参数里面的self会调用__iter__
            values = ', '.join('{}={!r}'.format(*i)
                               for i in zip(self.__slots, self))
            return '{}({})'.format(self.__class__.__name__, values)
    
        cls_attrs = dict(
            __slots__ = field_names,
            __init__ = __init__,
            __iter__ = __iter__,
            __repr__ = __repr__
        )
    
        return type(cls_name, (object,), cls_attrs)
    
    if __name__ == '__main__':
        ...
    

     这是通过函数返回一个类的类工厂函数。下面展示执行效果。

    >>> Dog = record_factory('Dog','name weight owner')
    >>> rex = Dog('rex', 30, 'sidian')
    >>> for i in rex:
    ...     print(i)
    ... 
    rex
    30
    sidian
    >>> rex
    Dog(name='rex', weight=30, owner='sidian')
    >>> Dog.__mro__
    (<class '__main__.Dog'>, <class 'object'>)
    >>> 
    

     执行中,报错了4次,丢人了,抄写个代码错了4次。

    我们可以把type当做函数,因为我们可以像函数那样使用它

    type(类名, 继承的父类(元祖形式), 一个映射(指定的新类的属性名和值))

    MyClass = type('MyClass', (object,), {'x': 42, 'x2': lambda self: self.x *2})
        
        class MyClass:
            x = 42
            def x2(self):
                return self.x * 2
    

     这里写了两个类的定义其实是一样的,有写书中说def, class只不过是Python的语法糖

    定义描述符的装饰器

    上一节最后一个任务就是给描述符属性定义了一个独特的名字,用于给托管实例添加属性。

    但那样的显示效果比较糟糕,我们不能使用存储属性名称,因为在实例化描述符时无法得知托管属性。

    书中的说明比较抽象,我的理解就时在类完成后,没有执行__init__初始化之前,调整描述符实例既托管类属性的初始化值。

    数中说使用__new__方法中无用,可能是翻译的问题,但我觉的__new__中也能实现,逻辑也时一样的。

    先按照书中通过装饰完成。

    def entity(cls):
        # 遍历类属性
        for key, attr in cls.__dict__.items():
            # 如果是Validate基类的属性
            if isinstance(attr, Validate):
                # type_name是实例类的名字
                type_name = type(attr).__name__
                # print(type_name)
                # key为描述符类实例的类属性赋值变量名
                attr.storage_name = '_{}#{}'.format(type_name, key)
                # print(attr.storage_name)
        return cls
    
    import model_v6 as model
    from model_v6 import Validate
    
    # @model.entity
    class LineItem:
        # 描述符实例赋值给托管类属性
        weight = model.Quantity()
        price = model.Quantity()
        description = model.NonBlack()
    
        def __new__(cls, *args, **kwargs):
            for key, attr in cls.__dict__.items():
                if isinstance(attr, Validate):
                    type_name = type(attr).__name__
                    attr.storage_name = '_{}#{}'.format(type_name, key)
            return super().__new__(cls)
    
        def __init__(self, description, weight, price):
            # 储存实例,托管实例中存储自身托管属性的属性 self.weight与self.price
            self.description = description
            # 这个按照书中的说法叫特性的赋值方法了,不是属性赋值了。
            self.weight = weight
            self.price = price
    
        def subtoall(self):
            return self.weight * self.price
    
    
    if __name__ == '__main__':
        line = LineItem('ok', 16, 17)
        print(vars(line))
        print(dir(line))
        # print(LineItem.description.storage_name)
    
    /usr/local/bin/python3.7 /Users/shijianzhong/study/Fluent_Python/第二十一章/bulkfood_v6.py
    {'_NonBlack#description': 'ok', '_Quantity#weight': 16, '_Quantity#price': 17}
    ['_NonBlack#description', '_Quantity#price', '_Quantity#weight', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'description', 'price', 'subtoall', 'weight']
    
    Process finished with exit code 0
    

     两种方式执行的效果时一样的,原来的描述符初始化的属性被手动的覆盖了。

    导入时和运行时比较。

    书中用了两个案例进行了比较。

    # 这个被导入肯定执行
    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')
    
    import abc
    
    
    # 创建一个自动管理和存储属性的描述符类
    class AutoStorage:
    
        __count = 0
    
        def __init__(self):
            # 描述符初始化赋值
            cls = self.__class__
            prefil = cls.__name__
            index = cls.__count
            # 设立一个独一无二的名称
            self.storage_name = '_{}#{}'.format(prefil, index)
            cls.__count += 1
    
        def __get__(self, instance, owner):
            if instance is None:
                return self
            else:
                return getattr(instance, self.storage_name)
    
        def __set__(self, instance, value):
            setattr(instance, self.storage_name, value)
    
    # 扩展AutoStorage类的抽象子类,覆盖__set__方法,调用必须由子类事项的validate
    class Validate(abc.ABC, AutoStorage):
    
        def __set__(self, instance, value):
            value = self.validate(instance, value)
            super().__set__(instance, value)
    
        @abc.abstractmethod
        def validate(self, instance, value):
            '''return validate value or raise ValueError'''
    
    # 给数字不能小于0的属性用的描述符
    class Quantity(Validate):
        def validate(self, instance, value):
            if value < 0 :
                raise ValueError('value must be > 0')
            return value
    
    # 给字段参数不能为0的属性用描述符
    class NonBlack(Validate):
        def validate(self, instance, value):
            if len(value) == 0:
                raise ValueError('value cannot be empty or blank')
            return value
    
    def entity(cls):
        # 遍历类属性
        for key, attr in cls.__dict__.items():
            # 如果是Validate基类的属性
            if isinstance(attr, Validate):
                # type_name是实例类的名字
                type_name = type(attr).__name__
                # print(type_name)
                # key为描述符类实例的类属性赋值变量名
                attr.storage_name = '_{}#{}'.format(type_name, key)
                # print(attr.storage_name)
        return cls
    

     当执行 import evaltime

    >>> import evaltime
    <[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
    <[14]> evaltime module end
    >>> 
    

     执行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]> ClassFour.method_y
    <[14]> evaltime module end
    <[4]> ClassOne.__del__
    

     从执行结果可以看出来,在导入时,解释器会执行每个类的定义体,甚至会执行嵌套类的定义体。执行类定义体的结果是,定义了类的属性和方法,并构建了类对象。

    元类基础知识

    元类时生产类的工厂。

    python的类和函数都时type类的实例。也可以认为类与函数都是type创建出来的。

    object类和type类之间的关系很独立:obejct是type的实例,而type是object的子类。这种关系很神奇,而且type是自身的实例。

    In [16]: import collections                                                                          
    
    In [17]: collections.Iterable.__class__                                                              
    /usr/local/bin/ipython:1: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
      #!/usr/local/opt/python/bin/python3.7
    Out[17]: abc.ABCMeta
    
    In [18]: import abc                                                                                  
    
    In [19]: abc.ABCMeta.__class__                                                                       
    Out[19]: type
    
    In [20]: abc.ABCMeta.__mro__                                                                         
    Out[20]: (abc.ABCMeta, type, object)
    
    In [21]:     
    

     从这个运行结果可以看出collections类由abc.ABCMeta创建。

    abc.ABCMeta是type的子类,而且也是type的实例。

    ABCmeta是元类。

    重点就是所有的类都是type的子类,但元类还是type的子类,元类因为继承了type所有可以创建类。

    理解元类计算时间的联系

    下面是测试代码:

    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')
    
    >>> 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__
    <[9]> ClassSix body
    <[500]> MetaAleph.__init__
    <[15]> evaltime_meta module end
    

     由结果可以看出,在执行import的时候,那些有元类创建的类,会调用元类的__init__方法,(__new__应该也会调用)

    后面继承了该类的类,也会同样调用父类的创建方式,这个是类装饰器做不到的。

    在__init__中,能够对已经创建的类进行属性的修改。

    shijianzhongdeMacBook-Pro:第二十一章 shijianzhong$ python3 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]> ClassFour.method_y
    <[14]> evaltime module end
    <[4]> ClassOne.__del__
    

     从运行结果来看,通过元类创建的类,经过元类的__init__函数修改,把原来的一个类属性(一个函数)进行了修改。

    如果想进一步定义类,可以在元类中实现__new__方法。不过通常下__init__方法就够了。

    # 这个被导入肯定执行
    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 __new__(cls, *args, **kwargs):
            # print(args, kwargs)
            def inner_2(self):
                print('<[600]> MetaAleph.__init__:inner_2')
            # 通过 __new__里面的参数修改最后一项字典参数里面修改原有的函数属性
            args[-1]['method_z'] = inner_2
            return super().__new__(cls, *args, **kwargs)
    
    
        def __init__(cls, name, bases, dic):
            # print(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')
    

     上面是通过__new__实现的,主要是通过修改args,那是需要被创建的类所携带的自身原来的类属性。

    定制描述符的元类。

    通过元类修改类属性描述符实例的属性。

    import abc
    
    
    # 创建一个自动管理和存储属性的描述符类
    class AutoStorage:
    
        __count = 0
    
        def __init__(self):
            # 描述符初始化赋值
            cls = self.__class__
            prefil = cls.__name__
            index = cls.__count
            # 设立一个独一无二的名称
            self.storage_name = '_{}#{}'.format(prefil, index)
            cls.__count += 1
    
        def __get__(self, instance, owner):
            if instance is None:
                return self
            else:
                return getattr(instance, self.storage_name)
    
        def __set__(self, instance, value):
            setattr(instance, self.storage_name, value)
    
    # 扩展AutoStorage类的抽象子类,覆盖__set__方法,调用必须由子类事项的validate
    class Validate(abc.ABC, AutoStorage):
    
        def __set__(self, instance, value):
            value = self.validate(instance, value)
            super().__set__(instance, value)
    
        @abc.abstractmethod
        def validate(self, instance, value):
            '''return validate value or raise ValueError'''
    
    # 给数字不能小于0的属性用的描述符
    class Quantity(Validate):
        def validate(self, instance, value):
            if value < 0 :
                raise ValueError('value must be > 0')
            return value
    
    # 给字段参数不能为0的属性用描述符
    class NonBlack(Validate):
        def validate(self, instance, value):
            if len(value) == 0:
                raise ValueError('value cannot be empty or blank')
            return value
    
    def entity(cls):
        # 遍历类属性
        for key, attr in cls.__dict__.items():
            # 如果是Validate基类的属性
            if isinstance(attr, Validate):
                # type_name是实例类的名字
                type_name = type(attr).__name__
                # print(type_name)
                # key为描述符类实例的类属性赋值变量名
                attr.storage_name = '_{}#{}'.format(type_name, key)
                # print(attr.storage_name)
        return cls
    
    class EntityMeta(type):
    
        def __init__(cls, name, bases, attr_dict):
            # 书中是先调用了超类的__init__方法
            # super().__init__(name, bases, attr_dict)
             # 创建类以后,修改类用的attr_dict参数,这里面是类属性的字典对照表,修改类属性的参数
            # 这个其实是一个猴子布丁,因为类是type的实例,修改type中的一些参数,将直接影响到实例
            for key ,attr in attr_dict.items():
                if isinstance(attr, Validate):
                    type_name = attr.__class__.__name__
                    attr.storage_name = '_{}#{}'.format(type_name, key)
            # 但我的理解,这句话写在最后比较更加符合我的理解,因为最后调用最新的参数,初始化创建的类。
            super().__init__(name, bases, attr_dict)
    
    
    # 这一步很骚,用到了Python的抽象作用,一个继承可以让调用者很方便
    class Entity(metaclass=EntityMeta):
        ...
    
    import model_v7 as model
    from model_v7 import Validate
    
    # @model.entity
    # 通过简单的继承,使用者不必知道内部复杂的逻辑。
    class LineItem(model.Entity):
        # 描述符实例赋值给托管类属性
        weight = model.Quantity()
        price = model.Quantity()
        description = model.NonBlack()
    
        # def __new__(cls, *args, **kwargs):
        #     for key, attr in cls.__dict__.items():
        #         if isinstance(attr, Validate):
        #             type_name = type(attr).__name__
        #             attr.storage_name = '_{}#{}'.format(type_name, key)
        #     return super().__new__(cls)
    
        def __init__(self, description, weight, price):
            # 储存实例,托管实例中存储自身托管属性的属性 self.weight与self.price
            self.description = description
            # 这个按照书中的说法叫特性的赋值方法了,不是属性赋值了。
            self.weight = weight
            self.price = price
    
        def subtoall(self):
            return self.weight * self.price
    
    
    if __name__ == '__main__':
        line = LineItem('ok', 16, 17)
        print(vars(line))
        print(dir(line))
        # print(LineItem.description.storage_name)
    

    元类的特殊方法__perpare__

    import abc
    
    
    # 创建一个自动管理和存储属性的描述符类
    class AutoStorage:
    
        __count = 0
    
        def __init__(self):
            # 描述符初始化赋值
            cls = self.__class__
            prefil = cls.__name__
            index = cls.__count
            # 设立一个独一无二的名称
            self.storage_name = '_{}#{}'.format(prefil, index)
            cls.__count += 1
    
        def __get__(self, instance, owner):
            if instance is None:
                return self
            else:
                return getattr(instance, self.storage_name)
    
        def __set__(self, instance, value):
            setattr(instance, self.storage_name, value)
    
    # 扩展AutoStorage类的抽象子类,覆盖__set__方法,调用必须由子类事项的validate
    class Validate(abc.ABC, AutoStorage):
    
        def __set__(self, instance, value):
            value = self.validate(instance, value)
            super().__set__(instance, value)
    
        @abc.abstractmethod
        def validate(self, instance, value):
            '''return validate value or raise ValueError'''
    
    # 给数字不能小于0的属性用的描述符
    class Quantity(Validate):
        def validate(self, instance, value):
            if value < 0 :
                raise ValueError('value must be > 0')
            return value
    
    # 给字段参数不能为0的属性用描述符
    class NonBlack(Validate):
        def validate(self, instance, value):
            if len(value) == 0:
                raise ValueError('value cannot be empty or blank')
            return value
    
    def entity(cls):
        # 遍历类属性
        for key, attr in cls.__dict__.items():
            # 如果是Validate基类的属性
            if isinstance(attr, Validate):
                # type_name是实例类的名字
                type_name = type(attr).__name__
                # print(type_name)
                # key为描述符类实例的类属性赋值变量名
                attr.storage_name = '_{}#{}'.format(type_name, key)
                # print(attr.storage_name)
        return cls
    
    class EntityMeta(type):
    
        @classmethod
        def __prepare__(mcs, name, bases):
            import collections
            print('__prepare__', name, bases)
            # 返回一个空的OrderedDict实例,类属性将存储在里面
            return collections.OrderedDict()
    
    
        def __init__(cls, name, bases, attr_dict):
            # 书中是先调用了超类的__init__方法
            # super().__init__(name, bases, attr_dict)
             # 创建类以后,修改类用的attr_dict参数,这里面是类属性的字典对照表,修改类属性的参数
            # 这个其实是一个猴子布丁,因为类是type的实例,修改type中的一些参数,将直接影响到实例
            cls._field_name = []
            for key ,attr in attr_dict.items():
                # 这一行与前期相比没有变化,不过这里的attr_dict是那个OrderedDict对象
                # 由解释器在调用__init__方法之前调用__prepare__方法时获得。因此,这个for循环会按照添加属性的顺序迭代属性
                if isinstance(attr, Validate):
                    type_name = attr.__class__.__name__
                    attr.storage_name = '_{}#{}'.format(type_name, key)
                    cls._field_name.append(key)
            # 但我的理解,这句话写在最后比较更加符合我的理解,因为最后调用最新的参数,初始化创建的类。
            super().__init__(name, bases, attr_dict)
    
    
    # 这一步很骚,用到了Python的抽象作用,一个继承可以让调用者很方便
    class Entity(metaclass=EntityMeta):
        @classmethod
        def field_names(cls):
            for name in cls._field_name:
                yield name
    

     type构造方法及元类的__new__和__init__方法都会收到要计算的类的定义体,形式是名称到属性的映象。然而在默认情况下,那个映射是字典,也就是说,元类或类装饰器获得映射时,属性在类定义体中中的顺序已经丢失了。

    这个问题的解决方法是,使用Python3引入的特殊方案__prepare__。这个特殊方法只在元类中有用,而且必须声明为类方法。

    解释器调用元类的__new__方法之前会先调用__prepare__方法,使用类定义体中的属性创建映射。

    __prepare__方法的第一个参数是元类,随后两个参数分别是要构建的类的名称和基类组成组成的元祖,返回值必须是映射。元类构建新类时,__prepare__方法返回的映射会传给__new__方案的最后一个参数,然后再传给__init__方法。

    元类的作用:

    验证属性

    一次把装饰器依附到多个方法上

    序列化对象或转换数据

    对象关系映射

    基于对象的持久存储

    动态转换使用其他语言编写的类结构

    类作为对象

    cls.__bases__

    由类的基类组成的元祖

    cls.__quanname

    python3.3新引入的属性,其值是函数或者限定名称,既从模块的全局作用域到类的点分路径。

    In [26]: import evaltime                                                                             
    
    In [27]: evaltime.ClassOne.ClassTwo.__qualname__                                                     
    Out[27]: 'ClassOne.ClassTwo'
    

    cls.__subclass__()

    这个方法返回一个列表,包含类的直接子类。这个方法的实现使用弱引用,防止再在超类和子类(子类在__bases__属性中存储指向超类的强引用)之间出现循环引用。

    这个方法返回的列表中是内存里现存的子类。

    cls.mro()

    构建类时,如果需要获取存储再类属性__mro__中的超类元祖,解释器会调用这个方法。

    元类可以覆盖这个方法,定制要构建的类解析方法的序列。

    dir(...)函数不会上面所提到的任何一个属性。

  • 相关阅读:
    编解码学习笔记(十):Ogg系列
    php大小写问题
    thinkphp5内置标签
    php递归实现无限级分类
    thinkphp5项目--企业单车网站(五)
    控制器不存在:appadmincontrollerDocument
    Cookie与Session的区别与联系及生命周期
    thinkphp5项目--企业单车网站(四)
    thinkphp5项目--企业单车网站(三)
    thinkphp使后台的字体图标显示异常
  • 原文地址:https://www.cnblogs.com/sidianok/p/12275184.html
Copyright © 2011-2022 走看看