Python属性描述符

属性描述符

描述符是对多个属性运用相同存取逻辑的一种方式。
描述符是实现了特定协议的类,包括__set____get____delete__方法。

描述符验证属性

特性函数工厂函数借助函数式编程模式避免重复编写读值方法和设值方法

描述符用法:创建一个实例,作为另一个类的类属性。

  • 使用Quantity描述符管理LineItem的属性
class Quantity:
    """描述符类,实现描述符协议的类"""
    
    def __init__(self, storage_name):
        self.storage_name = storage_name
        
    def __set__(self, instance, value): # self是LineItem.weight描述符实例,instance是LineItem托管实例
        if value > 0:
            instance.__dict__[self.storage_name] = value # 直接处理托管实例的___dict__属性,避免无限递归
        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
    
truffle = LineItem('White fruffle', 100, 0) # 小于0,抛出异常

  • 自动获取存取属性的名称
class Quantity:
    __counter = 0 # 类属性,统计实例数量
    
    def __init__(self):
        cls = self.__class__ # 类的引用
        prefix = cls.__name__
        index = cls.__counter
        self.storage_name = '_{}#{}'.format(prefix, index) # 生成独一无二的storage_name
        cls.__counter += 1
        
    def __get__(self, instance, owner): # owner是LineItem的引用 
        if instance is None: # 不是通过实例调用
            return self
        else:
            return getattr(instance, self.storage_name) # 从instance中获取存储属性的值
        
    def __set__(self, instance, value): # self是LineItem.weight,instance是LineItem实例
        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
    
    
cocounts = LineItem('Brazilian cocount', 20, 17.95)
cocounts.weight, cocounts.price
getattr(cocounts, '_Quantity#0'), getattr(cocounts, '_Quantity#1')

LineItem.price
br_nuts = Linetem('Brazil nuts', 10, 34.95)
br_nuts.price
  • 使用子类管理属性
import abc


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):
    """抽象类,处理验证"""
    
    def __set__(self, instance, value):
        value = self.validate(instance, value)
        super().__set__(instance, value)
        
    @abc.abstractmethod
    def validate(self, instance, value):
        """raise"""
        
        
class Quantity(Validated):
    """处理数值"""
    
    def validate(self, instance, value):
        if value <= 0:
            raise ValueError('value must be > 0')
        else:
            return value
        

class NonBlank(Validated):
    """处理description为空"""
    def validate(self, instance, value):
        value = value.strip()
        if len(value) == 0:
            raise ValueError('value cannot be empty or blank')
        return value
    
    
class LineItem:
    description = NonBlank()
    weight = Quantity()
    price = Quantity()
    
    def __init__(self, description, weight, price):
        self.description = description
        self.price = price
        self.weight = weight
        
    def subtotal(self):
        return self.weight * self.price

覆盖型和非覆盖型描述符对比

Python存取属性方式,通过实例读取属性时,通常返回的是实例中定义的属性;如果实例中没有指定的属性,会获取类属性。而实例中的属性赋值时,通常会在实例中创建属性,不影响类。

def cls_name(obj_or_cls):
    cls = type(obj_or_cls)
    if cls is type:
        cls = obj_or_cls
    return cls.__name__.split('.')[-1]


def display(obj):
    cls = type(obj)
    if cls is type:
        return '<clss {}>'.format(obj.__name__)
    elif cls in [type(None), int]:
        return repr(obj)
    else:
        return '<{} object>'.format(cls_name(obj))
    
    
def print_args(name, *args):
    pseudo_args = ', '.join(display(x) for x in args)
    print('-> {}.__{}__({})'.format(cls_name(args[0]), name, pseudo_args))
    
    
class Overriding:
    
    def __get__(self, instance, owner):
        print_args('get', self, instance, owner)
        
    def __set__(self, instance, value):
        print_args('set', self, instance, value)
        
        
class OverridingNoGet:
    
    def __set__(self, instance, value):
        print_args('set', self, instance, value)

        
class NonOverriding:
    def __get__(self, instance, owner):
        print_args('get', self, instance, owner)
        
        
class Managed:
    over = Overriding()
    over_no_get = OverridingNoGet()
    non_over = NonOverriding()
    
    def spam(self):
        print('-> Managed.spam({})'.foramt(display(self)))

覆盖型描述符

obj = Managed()
obj.over
-> Overriding.__get__(<Overriding object>, <Managed object>, <clss Managed>)

Managed.over # instance 托管实例是None
-> Overriding.__get__(<Overriding object>, None, <clss Managed>)

obj.over = 7 # __set__方法
-> Overriding.__get__(<Overriding object>, <Managed object>, 7)

obj.over # 描述符的__get__方法
-> Overriding.__get__(<Overriding object>, <Managed object>, <clss Managed>)

obj.__dict__['over'] = 8 # 跳过描述符,直接通过__dict__属性设值
vars(obj) # 在实例属性中
{'over': 8}

obj.over # Managed.over描述符会覆盖读取obj.over操作
-> Overriding.__get__(<Overriding object>, <Managed object>, <clss Managed>)

没有__get__方法的覆盖型描述符

  • 可以只实现__set__方法

只有写操作由描述符处理。

读操作时实例属性会遮盖描述符。

obj.over_no_get # 没有__get__方法,直接返回描述符对象本身
Managed.over_no_get

obj.over_no_get = 7 # 描述符的__set__方法
obj.over_no_get

obj.__dict__['over_no_get'] = 9 # 通过__dict__方法创建同名实例属性 
obj.over_no_get # 会直接返回新赋予的值

obj.over_no_get = 7 
obj.over_no_get # 仍然是返回新赋予的值'9'

非覆盖型描述符

  • 没有实现__set__方法的描述符是非覆盖型描述符

如果设置了同名的实例属性,描述符会被遮盖,致使描述符无法处理那个实例的那个属性。

obj = Managed()
obj.non_over
obj.non_over = 7
obj.non_over

Managed.non_over
del obj.non_over # 删除实例属性
obj.non_over # 触发的是描述符的__get__方法

在类中覆盖描述符

  • 为类属性赋值能覆盖描述符
obj = Managed()
Managed.over = 1
Managed.over_no_get = 2
Managed.non_over = 3
obj.over, obj.over_no_get, obj.non_over

方法是描述符

  • 方法是非覆盖型描述符
obj = Managed()
obj.spam # 绑定方法对象
Managed.spam # 函数

obj.spam = 7
obj.spam # 遮盖类属性,返回‘7’
import collections

class Text(collections.UserString):
    
    def __repr__(self):
        return 'Text({!r})'.format(self.data)
    
    def reverse(self):
        return self[::-1]
    

word = Text('forward')
word
word.reverse()
Text.reverse('backward') # 在类上调用方法,相当于调用函数
list(map(Text.reverse, ['repaid', (10, 20, 30), Text('stressed')])) # 可以处理Text实例之外的其他对象

Text.reverse.__get__(word) # 得到绑定的实例方法
Text.reverse.__get__(None, Text) # instance为None时,得到函数本身

word.reverse # 与Text.reverse.__get__(word) 一样
word.reverse.__self__
word.reverse.__func__ is Text.reverse 

总结

  • 描述符的用法,描述符的实例作为另一个类的类属性
  • 描述符替换特性
  • 描述符用于验证属性,扩展子类用于验证属性是否为空
  • 类中计数器生成独一无二的storage_name
  • 覆盖型描述符和非覆盖型描述符的差异,实现__set__方法的属于覆盖型描述符
  • 方法是非覆盖型描述符。通过实例访问依附在类上的函数时,由描述符协议的处理,就会变成方法。

流畅的Python2015

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值