zoukankan      html  css  js  c++  java
  • 流畅的python第二十章属性描述符学习记录

    描述符是对多个属性运用相同存取逻辑的一种方式。例如,Django ORM 和 SQL Alchemy
    等 ORM 中的字段类型是描述符,把数据库记录中字段里的数据与 Python 对象的属性对应
    起来。
    描述符是实现了特定协议的类,这个协议包括 __get__、__set__ 和 __delete__ 方
    法。property 类实现了完整的描述符协议。通常,可以只实现部分协议。其实,我们在
    真实的代码中见到的大多数描述符只实现了 __get__ 和 __set__ 方法,还有很多只实现
    了其中的一个。

    描述符是 Python 的独有特征,不仅在应用层中使用,在语言的基础设施中也有用到。除
    了特性之外,使用描述符的 Python 功能还有方法及 classmethod 和 staticmethod 装饰
    器。理解描述符是精通 Python 的关键。本章的话题就是描述符。

    实现了 __get__、__set__ 或 __delete__ 方法的类是描述符。描述符的用法是,创建
    一个实例,作为另一个类的类属性。

    class Quantity:
        
        def __init__(self, storage_name):
            self.storage_name = storage_name
            
        def __set__(self, instance, value):
            if value > 0:
                instance.__dict__[self.storage_name] = value
            else:
                raise ValueError('value must be > 0')
            
            
    class LineItem:
        weight = Quantity('weight')
        price = Quantity('price')
        
        def __init__(self, description, weight, price):
            self.description = description
            self.weight = weight
            self.price = price
            
        def subtotal(self):
            return self.weight * self.price

    改进版

    class 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, owner):
            return getattr(instance, self.storage_name)
        
        def __set__(self, instance, value):
            if value > 0:
                setattr(instance, self.storage_name, value)
            else:
                raise ValueError('value must be > 0')
    
    
    class LineItem:
        weight = Quantity()
        price = Quantity()
    
        def __init__(self, description, weight, price):
            self.description = description
            self.weight = weight
            self.price = price
    
        def subtotal(self):
            return self.weight * self.price

    使用特性工厂函数实现

    def quantity():
        try:
            quantity.counter += 1
        except AttributeError:
            quantity.counter = 0
            
        storage_name = '_{}:{}'.format('quantity', quantity.counter)
        
        def qty_getter(instance):
            return getattr(instance, storage_name)
    
        def qty_setter(instance, value):
            if value > 0:
                setattr(instance, storage_name, value)
            else:
                raise ValueError('value must be > 0')
    
        return property(qty_getter, qty_setter)

    更深入的例子

    class AutoStorage:#最基础的类,负责存储属性获取属性值和设置属性值
        __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, 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)
            
            
    class Validated(abc.ABC, AutoStorage):#抽象基类,子类必须实现validate方法,用来验证设置值是否有效
        
        def __set__(self, instance, value):
            value = self.validate(instance, value)
            super().__set__(instance, value)
            
        @abc.abstractmethod
        def validate(self, instance, value):
            """return validated value or raise ValueError"""
            
            
    class Quantity(Validated):# 验证设置值是否大于0的验证类
        
        def validate(self, instance, value):
            if value <= 0:
                raise ValueError('value must be > 0')
            return value
        
        
    class NonBlank(Validated):# 验证设置值是否为空的验证类
        
        def validate(self, instance, value):
            value = value.strip()
            if len(value) == 0:
                raise ValueError('value cannot be empty or blank')
            return value

    覆盖型描述符
    实现 __set__ 方法的描述符属于覆盖型描述符,因为虽然描述符是类属性,但是实现
    __set__ 方法的话,会覆盖对实例属性的赋值操作

    特性也
    是覆盖型描述符:如果没提供设值函数,property 类中的 __set__ 方法会抛出
    AttributeError 异常,指明那个属性是只读的。

    没有 __get__ 方法的覆盖型描述符
    通常,覆盖型描述符既会实现 __set__ 方法,也会实现 __get__ 方法,不过也可以只实
    现 __set__ 方法,如示例 20-1 所示。此时,只有写操作由描述符处理。通过实例读取描
    述符会返回描述符对象本身,因为没有处理读操作的 __get__ 方法。如果直接通过实例
    的 __dict__ 属性创建同名实例属性,以后再设置那个属性时,仍会由 __set__ 方法插
    手接管,但是读取那个属性的话,就会直接从实例中返回新赋予的值,而不会返回描述符
    对象。也就是说,实例属性会遮盖描述符,不过只有读操作是如此

    非覆盖型描述符
    没有实现 __set__ 方法的描述符是非覆盖型描述符。如果设置了同名的实例属性,描述
    符会被遮盖,致使描述符无法处理那个实例的那个属性。方法是以非覆盖型描述符实现

    描述符用法建议

    使用特性以保持简单
      内置的 property 类创建的其实是覆盖型描述符,__set__ 方法和 __get__ 方法都
    实现了,即便不定义设值方法也是如此。特性的 __set__ 方法默认抛出
    AttributeError: can't set attribute,因此创建只读属性最简单的方式是使用特
    性,这能避免下一条所述的问题。

    只读描述符必须有 __set__ 方法
      如果使用描述符类实现只读属性,要记住,__get__ 和 __set__ 两个方法必须都定
    义,否则,实例的同名属性会遮盖描述符。只读属性的 __set__ 方法只需抛出
    AttributeError 异常,并提供合适的错误消息。

    用于验证的描述符可以只有 __set__ 方法
      对仅用于验证的描述符来说,__set__ 方法应该检查 value 参数获得的值,如果有
    效,使用描述符实例的名称为键,直接在实例的 __dict__ 属性中设置。这样,从实例中
    读取同名属性的速度很快,因为不用经过 __get__ 方法处理

    仅有 __get__ 方法的描述符可以实现高效缓存
      如果只编写了 __get__ 方法,那么创建的是非覆盖型描述符。这种描述符可用于执
    行某些耗费资源的计算,然后为实例设置同名属性,缓存结果。同名实例属性会遮盖描述
    符,因此后续访问会直接从实例的 __dict__ 属性中获取值,而不会再触发描述符的
    __get__ 方法。
    非特殊的方法可以被实例属性遮盖
      由于函数和方法只实现了 __get__ 方法,它们不会处理同名实例属性的赋值操作。
    因此,像 my_obj.the_method = 7 这样简单赋值之后,后续通过该实例访问
    the_method 得到的是数字 7——但是不影响类或其他实例。然而,特殊方法不受这个问
    题的影响。解释器只会在类中寻找特殊的方法,也就是说,repr(x) 执行的其实是
    x.__class__.__repr__(x),因此 x 的 __repr__ 属性对 repr(x) 方法调用没有影响。
    出于同样的原因,实例的 __getattr__ 属性不会破坏常规的属性访问规则。

  • 相关阅读:
    洛谷P2912 [USACO08OCT]牧场散步Pasture Walking [2017年7月计划 树上问题 01]
    洛谷P1082 同余方程 [2012NOIP提高组D2T1] [2017年6月计划 数论06]
    洛谷P2667 超级质数 [2017年6月计划 数论05]
    洛谷P1965 转圈游戏 [2013NOIP提高组 D1T1][2017年6月计划 数论04]
    洛谷P1595 信封问题
    洛谷P1062 数列 [2017年6月计划 数论03]
    洛谷P2835 刻录光盘 [2017年6月计划 强连通分量02]
    洛谷P2826 [USACO08NOV]光开关Light Switching [2017年6月计划 线段树02]
    【模板】矩阵快速幂 洛谷P2233 [HNOI2002]公交车路线
    【模板】ST表 洛谷P1816 忠诚
  • 原文地址:https://www.cnblogs.com/lgh344902118/p/8404904.html
Copyright © 2011-2022 走看看