learn from 《流畅的python》
1. 描述符示例:验证属性
- 描述符是对多个属性 运用 相同存取逻辑的一种方式
- 描述符是实现了 特定协议 的类,这个协议包括
__get__、__set__ 和 __delete__ 方法 property 类实现了完整的描述符协议
实现了 __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 greater than 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
为了避免 price = Quantity('weight') 这样的错误: 采用如下改进:
2. 自动获取储存属性的名称
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 __set__(self, instance, value):
if value > 0:
setattr(instance, self.storage_name, value)
else:
raise ValueError("value must be greater than 0")
def __get__(self, instance, owner):
return getattr(instance, self.storage_name)
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
coconuts = LineItem('Brazilian coconut', 20, 17.95)
print(coconuts.weight, coconuts.price)
print(getattr(coconuts, '_Quantity#0'), getattr(coconuts, '_Quantity#1'))
print(LineItem.weight)
但是上面的报错信息,让人困惑,如何修改,最好让 __get__ 方法返回描述符实例
def __get__(self, instance, owner):
if instance is None:
return self
return getattr(instance, self.storage_name)
print(LineItem.weight)
3. 继承改进
import abc
class AutoStorge:
__counter = 0
def __init__(self):
cls = self.__class__
prefix = cls.__name__
index = cls.__counter
self.storage_name = "_{}#{}".format(prefix, index)
cls.__counter += 1
def __set__(self, instance, value):
setattr(instance, self.storage_name, value)
def __get__(self, instance, owner):
if instance is None:
return self
return getattr(instance, self.storage_name)
class Validated(abc.ABC, AutoStorge):
def __set__(self, instance, value):
value = self.validate(instance, value)
super().__set__(instance, value)
@abc.abstractmethod
def validate(self, instance, value):
"""to do"""
class Quantity(Validated):
def validate(self, instance, value):
if value <= 0:
raise ValueError("value should be positive")
return value
class NonBlank(Validated):
"""a string with at least one non-space character"""
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.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price
coconuts = LineItem(' Brazilian coconut ', 20, 17.95)
print(coconuts.weight, coconuts.price)
print(getattr(coconuts, '_NonBlank#0'))
print(getattr(coconuts, '_Quantity#0'), getattr(coconuts, '_Quantity#1'))
print(LineItem.weight)
- 描述符的典型用途——
管理 数据属性 这种描述符也叫覆盖型 描述符,因为描述符的 __set__ 方法使用托管实例中的同名属性覆盖(即插手接管) 了要设置的属性
4. 覆盖型与非覆盖型描述符对比
通过实例读取属性时, 通常返回的是实例中定义的属性; 但是,如果实例中没有指定的属性, 那么会获取类属性。 而为实例中的属性赋值时,通常会在实例中创建属性,根本不影响类
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 '<class {}>'.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:
"""没有``__get__``方法的覆盖型描述符"""
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({})'.format(display(self)))
4.1 覆盖型描述符
obj = Managed()
obj.over
Managed.over
obj.over = 7
obj.over
obj.__dict__['over'] = 8
print(vars(obj))
obj.over
4.2 没有 __get__ 方法的覆盖型描述符
- 只实现
__set__ 方法,只有 写操作 由描述符处理。 - 通过实例读取描述符会返回 描述符对象本身,因为没有处理读操作的
__get__ 方法。 - 如果直接通过实例的
__dict__ 属性创建同名实例属性,以后再设置那个属性时,仍会由 __set__ 方法 插手接管,但是读取那个属性的话,就会直接从实例中返回新赋予的值,而不会返回描述符对象。也就是说,实例属性会遮盖描述符,不过 只有读操作是如此
print(obj.over_no_get)
print(Managed.over_no_get)
obj.over_no_get = 7
print(obj.over_no_get)
obj.__dict__['over_no_get'] = 9
print(obj.over_no_get)
obj.over_no_get = 7
print(obj.over_no_get)
4.3 非覆盖型描述符
没有实现 __set__ 方法的描述符是 非覆盖 型描述符。 如果设置了同名 的实例属性,描述符会被遮盖,致使描述符 无法处理 那个实例的那个属性
obj.non_over
obj.non_over = 7
print(obj.non_over)
Managed.non_over
del obj.non_over
obj.non_over
4.4 在类中覆盖描述符
- 不管 描述符 是不是覆盖型,为 类属性
赋值 都能 覆盖 描述符
obj = Managed()
Managed.over = 1
Managed.over_no_get = 2
Managed.non_over = 3
print(obj.over, obj.over_no_get, obj.non_over)
5. 描述符用法建议
- 创建只读属性最简单的方式是 使用特性 property
- 使用 描述符类 实现只读属性,要记住,
__get__ 和 __set__ 两个方法必须都定义,否则,实例的同名属性会遮盖描述符 - 用于 验证的 描述符 可以 只有
__set__ 方法 - 仅有
__get__ 方法的描述符 可以实现 高效缓存 - 非特殊的方法 可以被 实例属性遮盖
|