流畅的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(...)函数不会上面所提到的任何一个属性。

 

posted @ 2020-02-07 23:40  就是想学习  阅读(320)  评论(0编辑  收藏  举报