读书笔记:《流畅的Python》第21章 类元编程

# 第21章 类元编程

"""
类元编程指的是运行时创建或定制类的技艺
    1.类是一等对象,任何时候都可以使用函数新建类,而无需使用class关键字
    2.类装饰器也是函数,不过能够审查/修改/甚至把被装饰的类替换成其他类
    3.元类功能强大,但难以掌握,类装饰器能用更简单的方式解决很多问题
"""

# 21.1 类工厂函数
# collections.namedtuple:
#     把一个类名和几个属性名传给这个函数,它会创建一个tuple的子类,
#     其中的元素通过名称获取,还为调式提供了友好的字符串表示形式(__repr__)
# 使用类似于collections.namedtuple工厂函数的方式,建立一个类工厂函数
# 21-2 record_factory.py:一个简单的类工厂函数

# type:
"""
通常,我们把type()视为函数,调用type(my_object)获取对象所属的类
效果和my_object.__class__一样,
然而type是一个类,当作类使用时传入三个参数可以新建一个类:
    MyClass = type('MyClass',(MySuperClass,MyMixin),
                    {'x':42,'x2':lambda self :self.x*2})
三个参数分别是 name/base/dict
    dict是映射,指定新类的属性和值
    上述代码等价于
    
    class MyClass(MySuperClass,MyMixin):
        x = 42
        def x2(slef):
            return self.x *2
type的实例是类,type是自身的实例
把三个参数传给type是动态创建类的常用方式
collctions.namedtuple使用的是另一种方式:
    先申明一个_class_template变量,其值是字符串形式的源码模版,
    然后namedtuple函数调用_class_template的format方法,填充模版里的空白
    最后使用内置的exec函数计算得到的源码字符串
"""

# 21.2 定制描述符的类装饰器
"""
类装饰器和函数装饰非常类似,是参数为类对象的函数,返回原来的类或修改后的类"""

#  示例 21-3 bulkfood_v6.py:使用Quantity和NonBlank描述符的LineItem类
#  示例 21-4 model_v6.py :一个简单的类装饰器

"""
类装饰器有个重大缺陷:
    只对直接依附的类有效,被装饰的类的子类可能继承也可能不会继承装饰器所做的改动
    具体情况视改动的方式而定
"""

# 21.3导入时和运行时的比较
"""
导入时和运行时:
    python解释器会从上到下一次性解析完.py的源码,然后生成用于执行的字节码
    如果句法有错误,就在此时报告
    如果本地的__pycache__文件夹中有最新的.pyc文件,则跳过上述步骤,因为已经有运行所需的字节码了
    编译肯定是导入时的活动,不过那个时期还会做其他事情,
        因为python语句几乎都是可执行的,也就是说语句可能会运行用户代码,修改用户程序状态,
        尤其是import语句,它不只是申明
    在用户首次导入模块时,还会运行模块中的全部顶层代码--以后带入相同的模块则使用缓存,只做名称绑定.
        哪些顶层代码可以做任何事情,包括通常在运行时做的事情,比如连接数据库
        
    对于模块中的函数.运行顶层代码时,会编译函数的定义体,但并不会执行,仅当在运行时调用函数才执行
    
    对于类来说,情况就不同了
        在导入时,解释器会执行每个类的定义体,甚至会执行嵌套类的定义体
        结果是定义了类的属性和方法,并构建了类对象
"""

# 理解计算时间的练习
# 示例21-6 evaltime.py 按顺序写出输出的序号标记<[N]>

# 21.4 元类的基础知识
"""
元类是制造类的工厂,不过不是函数而是类
根据Python对象模型,类是对象,因此类肯定是另外某个类的实例
默认情况下,python中的类是type类的实例,即
type类是大多数内置的类和用户自定义的类的元类

object和type的关系:
    object是type的实例,
    type是object的子类
    
所有的类都直接或者间接地是type的实例,不过只有元类同时也是type的子类
"""
print('spam'.__class__)  # <class 'str'>
print(str.__class__)  # <class 'type'>
print(type.__class__)  # <class 'type'> type是其自身的实例

# 其他元类 abc.ABCMeta
import collections

print(collections.Iterable.__class__)  # <class 'abc.ABCMeta'>
import abc

print(abc.ABCMeta.__class__)  # <class 'type'>
print(abc.ABCMeta.__mro__)

"""
元类可以通过实现__init__方法定制实例,元类的__init__方法可以做到
类装饰器能做的任何事情,但是作用更大"""

# 理解元类计算时间的练习
# 示例21-10 evaltime_meta.py :ClassFive是MetaAleph元类的实例

# 21.5 定制描述符的元类
# 示例 21-14 bulkfood_v7.py : 有元类支持,继承model.Entity类即可
# 示例 21-15 model_v7.py:EntityMeta元类以及它的一个实例


# 21.6元类的特殊方法__prepare__
# 示例21-16 model_v8.py:这一版的EntityMeta用到了__prepare__方法,而且为Entity定义了field_names类方法

"""
元类的作用:
    1.验证属性
    2.一次把装饰器依附到多个方法上
    3.序列化对象或者转换数据
    4.对象关系映射
    5.基于对象的持久存储
    6.动态转换使用其他语言编写的类结构
"""

#21.7 类作为对象
"""
python数据模型为每个类定义了很多属性
    __mro__
    __class__
    __name__
    cls.__bases:由类的基类组成的元组
    cls.__qualname__:其值是类或者函数的限定名称,即从模块的全局作用域到类的点分路径
    cls.__subclasses__():这个方法返回一个列表,包含类的直接子类,这个方法的实现使用弱引用
    cls.mro():构建类时,如果需要获取存储在类属性__mro__中的超类元组,解释器会调用这个方法,定制
                要构建类的解析方法顺序"""


# 21-2 record_factory.py:一个简单的类工厂函数
# 21-2 record_factory.py:一个简单的类工厂函数
def record_factory(cls_name,field_names):
    try:
        field_names = field_names.replace(',',' ').split()  # 1
    except AttributeError:  # 不能调用replace或者split方法
        pass # 假定field_names本就是标识符组成的序列

    field_names = tuple(field_names)  # 2

    def __init__(self,*args,**kwargs):  # 3
        attrs = dict(zip(self.__slots__,args))
        attrs.update(kwargs)
        for name,value in attrs.items():
            setattr(self,name,value)
    def __iter__(self):  # 4
        for name in self.__slots__:
            yield getattr(self,name)
    def __repr__(self):  # 5
        values = ', '.join('{}={!r}'.format(*i)
                           for i in zip(self.__slots__,self))
        return '{}({})'.format(self.__class__.__name__,values)

    cls_attrs = dict(__slots__ = field_names,  # 6
                     __init__ = __init__,
                     __iter__ = __iter__,
                     __repr__ = __repr__)

    return type(cls_name,(object,),cls_attrs)  # 7

"""
说明:
    1.这里体现了鸭子类型:尝试在逗号或者空格拆分field_names,如果失败,那么假定field_names
    本就是可迭代的对象,一个元素对应一个属性名
    2.使用属性名构建元组,浙江成为新建类的__slots__属性;此外这个么做,还设定了拆包的顺序和字符串
    表示形式中各字段的顺序.
    3.这个函数将成为新建类的__init__方法,参数有位置参数或关键字参数
    4.实现了__iter__方法,把类的实例变成可迭代的对象;按照__slots__的顺序产出字段值
    5.迭代__slots__和self,生成友好的字符串表示形式
    6.组建类属性字典
    7.调用type构造方法,构建新类,返回将其返回
 
"""
if __name__ == '__main__':
    Dog = record_factory('Dog','name weight owner')
    rex = Dog('REX',30,'Bob','color'=='white')  #  可以这样写,但是不能把属性加入实例的属性
    # print(rex.color)  # 报错
    print(repr(rex))
    # print(rex.__slots__)
    # print(Dog.__dict__)
    name,weight,_ = rex  # 实例是可迭代的对象
    print(name,weight)
    print("{2}'s dog weight {1}kg".format(*rex))  # 也可以拆包
    rex.weight = 32  # 实例是可变的对象
    print(repr(rex))
    print(Dog.__mro__)  # 新类继承自object和工厂函数没有关系

#  示例 21-3 bulkfood_v6.py:使用Quantity和NonBlank描述符的LineItem类
#  示例 21-3 bulkfood_v6.py:使用Quantity和NonBlank描述符的LineItem类

import model_v6 as model
@model.entity  #添加了类装饰器
class LineItem:
    weight = model.Quantity()
    price = model.Quantity()
    description = model.NonBlank()

    def __init__(self,description,weight,price):
        self.description = description
        self.weight = weight
        self.price = price

    def subtotal(self):
        return self.weight * self.price

if __name__ == '__main__':

    raisins = LineItem('Golden raisins',10,6.95)
    print(dir(raisins))  # '_NonBlank#description', '_Quantity#price', '_Quantity#weight'
    print(LineItem.description.storage_name)  #_NonBlank#description
    print(raisins.description)
    print(getattr(raisins, '_NonBlank#description'))
#  示例 21-4 model_v6.py :一个简单的类装饰器
#  示例 21-4 model_v6.py :一个简单的类装饰器


import abc
class AutoStorage:  #1 AutoStorage提供了之前Quantity的大部分功能
    __counter = 0
    def __init__(self):
        cls = self.__class__
        prefix = cls.__name__
        index = cls.__counter
        self.storage_name = '_{}#{}'.format(prefix,index)
        cls.__counter += 1

    def __get__(self,instance,ower):
        # ower是托管类的引用
        if instance is None:
            return self  # 如果不是通过实例调用,返回描述符自身
        else: # 否则像之前一样返回托管属性的值
            return getattr(instance,self.storage_name)

    def __set__(self, instance, value):
        setattr(instance,self.storage_name,value) #2 ..验证部分除外

class Validated(abc.ABC,AutoStorage):  # 3 抽象类也继承AutoStorage
    def __set__(self, instance, value):
        value = self.validate(instance,value)  # 4 把验证操作委托给validate方法
        super().__set__(instance,value)  # 5 然后把返回的值传给超类的__set__方法
    @abc.abstractmethod
    def validate(selfs,instance,value):  # 6 validate是抽象方法
        '''return validated value or raise ValueError'''

class Quantity(Validated):  # 7 Quantity和NonBlank都继承自Validated
    '''a number greater than zero'''
    def validate(selfs,instance,value):
        if value <= 0:
            raise ValueError('value must be > 0 ')
        return value

class NonBlank(Validated):
    '''a string with at least one non-space character'''
    def validate(selfs,instance,value):
        value = value.strip()
        if len(value) == 0:
            raise ValueError('value cannot be empty or blank')
        return value  # 8 要求具体的方法返回验证后的值,借机可以清理\转换和规范化接收的数据
"""===============以上为modle_v5.py的代码=================="""

def entity(cls):  # 1
    for key,attr in cls.__dict__.items(): # 2
        if  isinstance(attr,Validated):  # 3
            type_name = type(attr).__name__
            attr.storage_name = '_{}#{}'.format(type_name,key)  # 4
    return cls  # 5

"""
说明:
    1.装饰器的参数是一个类
    2.迭代存储类属性的字典
    3.如果属性是Validated描述符实例
    4.使用买描述符类的托管属性的名称命名storage_name(例如_NonBlank#description)
    5.返回修改后的类


"""
# 示例21-6 evaltime.py 按顺序写出输出的序号标记<[N]>
from evalsupport import deco_alpha
print('<[1]> evaltime module start ')
class ClassOne:
    print('<[2]> CalssOne body ')
    def __init__(self):
        print('<[3]> CalssOne.__init__')

    def __del__(self):
        print('<[4]> CalssOne.__del__')

    def method_x(self):
        print('<[5]> CalssOne.method_x')

    class ClassTwo(object):
        print('<[6]> CalssTwo body ')

@deco_alpha
class ClassThree():
    print('<[7]> ClassThree body ')
    def method_y(self):
        print('<[8]> CalssThree.method_y')

class ClassFour(ClassThree):
    print('<[9]> ClassFour body ')
    def method_y(self):
        print('<[10]> CalssFour.method_y')

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')

evalsupport.py
print('<[100]> evalsuport 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]> evalsuport module end')

# 示例21-10 evaltime_meta.py :ClassFive是MetaAleph元类的实例
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]> CalssThree.method_y')

class ClassFour(ClassThree):
    print('<[4]> ClassFour body ')
    def method_y(self):
        print('<[5]> CalssFour.method_y')

class ClassFive(metaclass=MetaAleph):
    print('<[6]> ClassFive body ')
    def method_y(self):
        print('<[7]> CalssFive.method_y')
    def method_z(self):
        print('<[8]> CalssFive.method_z')

class ClassSix(ClassFive):
    print('<[9]> ClassSix body ')
    def method_z(self):
        print('<[10]> CalssSix.method_z')

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')
# 示例 21-14 bulkfood_v7.py : 有元类支持,继承model.Entity类即可
# 示例 21-14 bulkfood_v7.py : 有元类支持,继承model.Entity类即可

# import model_v7 as model
import model_v8 as model
class LineItem(model.Entity):  # LineItem是model.Entity的子类
    weight = model.Quantity()
    price = model.Quantity()
    description = model.NonBlank()

    def __init__(self,description,weight,price):
        self.description = description
        self.weight = weight
        self.price = price

    def subtotal(self):
        return self.weight * self.price

if __name__ == '__main__':

    raisins = LineItem('Golden raisins',10,6.95)
    print(dir(raisins))  # '_NonBlank#description', '_Quantity#price', '_Quantity#weight'
    print(LineItem.description.storage_name)  #_NonBlank#description
    print(raisins.description)
    print(getattr(raisins, '_NonBlank#description'))

    # model_v8的新功能
    for name in LineItem.field_names():
        print(name)
# 示例 21-15 model_v7.py:EntityMeta元类以及它的一个实例
# 示例 21-15 model_v7.py:EntityMeta元类以及它的一个实例

import abc
class AutoStorage:  #1 AutoStorage提供了之前Quantity的大部分功能
    __counter = 0
    def __init__(self):
        cls = self.__class__
        prefix = cls.__name__
        index = cls.__counter
        self.storage_name = '_{}#{}'.format(prefix,index)
        cls.__counter += 1

    def __get__(self,instance,ower):
        # ower是托管类的引用
        if instance is None:
            return self  # 如果不是通过实例调用,返回描述符自身
        else: # 否则像之前一样返回托管属性的值
            return getattr(instance,self.storage_name)

    def __set__(self, instance, value):
        setattr(instance,self.storage_name,value) #2 ..验证部分除外

class Validated(abc.ABC,AutoStorage):  # 3 抽象类也继承AutoStorage
    def __set__(self, instance, value):
        value = self.validate(instance,value)  # 4 把验证操作委托给validate方法
        super().__set__(instance,value)  # 5 然后把返回的值传给超类的__set__方法
    @abc.abstractmethod
    def validate(selfs,instance,value):  # 6 validate是抽象方法
        '''return validated value or raise ValueError'''

class Quantity(Validated):  # 7 Quantity和NonBlank都继承自Validated
    '''a number greater than zero'''
    def validate(selfs,instance,value):
        if value <= 0:
            raise ValueError('value must be > 0 ')
        return value

class NonBlank(Validated):
    '''a string with at least one non-space character'''
    def validate(selfs,instance,value):
        value = value.strip()
        if len(value) == 0:
            raise ValueError('value cannot be empty or blank')
        return value  # 8 要求具体的方法返回验证后的值,借机可以清理\转换和规范化接收的数据
"""===============以上为modle_v5.py的代码=================="""

class EntityMeta(type):
    '''元类,用于创建带验证字段的业务实体'''
    def __init__(cls,name,bases,attr_dict):
        super().__init__(name,bases,attr_dict)  # 1 在超类type上调用__init__方法
        for key,attr in attr_dict.items():  # 2 与类装饰器的逻辑一样
            if isinstance(attr,Validated):
                type_name = type(attr).__name__
                attr.storage_name = '_{}#{}'.format(type_name,key)
class Entity(metaclass=EntityMeta):  # 这个类的存在只是为了用起来便利,这个模块的用户直接继承Entity即可
    '''带有验证字段的实体业务'''

# 示例21-16 model_v8.py:这一版的EntityMeta用到了__prepare__方法,而且为Entity定义了field_names类方法
# 示例21-16 model_v8.py:这一版的EntityMeta用到了__prepare__方法,而且为Entity定义了field_names类方法
# 示例 21-15 model_v7.py:EntityMeta元类以及它的一个实例

import abc
import collections
class AutoStorage:  #1 AutoStorage提供了之前Quantity的大部分功能
    __counter = 0
    def __init__(self):
        cls = self.__class__
        prefix = cls.__name__
        index = cls.__counter
        self.storage_name = '_{}#{}'.format(prefix,index)
        cls.__counter += 1

    def __get__(self,instance,ower):
        # ower是托管类的引用
        if instance is None:
            return self  # 如果不是通过实例调用,返回描述符自身
        else: # 否则像之前一样返回托管属性的值
            return getattr(instance,self.storage_name)

    def __set__(self, instance, value):
        setattr(instance,self.storage_name,value) #2 ..验证部分除外

class Validated(abc.ABC,AutoStorage):  # 3 抽象类也继承AutoStorage
    def __set__(self, instance, value):
        value = self.validate(instance,value)  # 4 把验证操作委托给validate方法
        super().__set__(instance,value)  # 5 然后把返回的值传给超类的__set__方法
    @abc.abstractmethod
    def validate(selfs,instance,value):  # 6 validate是抽象方法
        '''return validated value or raise ValueError'''

class Quantity(Validated):  # 7 Quantity和NonBlank都继承自Validated
    '''a number greater than zero'''
    def validate(selfs,instance,value):
        if value <= 0:
            raise ValueError('value must be > 0 ')
        return value

class NonBlank(Validated):
    '''a string with at least one non-space character'''
    def validate(selfs,instance,value):
        value = value.strip()
        if len(value) == 0:
            raise ValueError('value cannot be empty or blank')
        return value  # 8 要求具体的方法返回验证后的值,借机可以清理\转换和规范化接收的数据
"""===============以上为modle_v5.py的代码=================="""

class EntityMeta(type):
    '''元类,用于创建带验证字段的业务实体'''
    @classmethod
    def __prepare__(cls, name, bases):
        return collections.OrderedDict()  # 返回一个空的OrderedDict实例,类属性将存储在里面
    def __init__(cls,name,bases,attr_dict):
        super().__init__(name,bases,attr_dict)
        cls._field_names = []  # 在要构建的类中创建一个_field_names属性
        for key,attr in attr_dict.items(): # 这里的 attr_dict是一个OrderedDict对象,会按顺序迭代
            if isinstance(attr,Validated):
                type_name = type(attr).__name__
                attr.storage_name = '_{}#{}'.format(type_name,key)
                cls._field_names.append(key)  # 把找到的各个Validated字段添加到_field_names中
class Entity(metaclass=EntityMeta):  # 这个类的存在只是为了用起来便利,这个模块的用户直接继承Entity即可
    '''带有验证字段的实体业务'''
    @classmethod
    def field_names(cls):
        for name in cls._field_names:
            yield name  # 按照添加字段的顺序产出字段的名称


35岁学python,也不知道为了啥?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值