Chapter 19 - Dynamic Attributes and Properties
数据的属性和处理数据的方法统称属性,方法只是可调用的属性。还可以创建特性,使用存取方法修改数据属性。
19.1 使用动态属性转换数据
warnings.warn(str)可以用来发出提醒(Warning);- 可以在
with语句中使用两个上下文管理器(如同时读取和保存文件),语句为with A as a, B as b。
使用动态属性访问数据
- JSON 格式中,只有字典和列表是集合类型;
- 可以自定义
__getattr__方法来支持使用动态属性。
def __getattr__(self, name):
if hasattr(self.__data, name):
return getattr(self.__data, name)
else:
return FrozenJSON.build(self.__data[name])
处理无效属性名
- 如果属性名要用到关键字(可用
keyword.iskeyword()函数判断),则需要特殊处理,如在键名后加上'_'符号; - 如果属性名要用到非标识符,可以使用
str.isidentifier()方法判断,并将其变为有效的属性名,如使用通用名称attr_xx等。
使用 __new__ 创建对象
- 在构造实例时,真正使用的方法的是类的特殊方法
__new__(cls, arg)(一般从object类继承),无需使用@classmethod装饰,必须返回一个实例。如果返回的实例是该类的实例,则会调用其__init__方法完成实例的初始化(__init__方法不允许返回值)。
def object_maker(the_class, some_arg):
new_object = the_class.__new__(some_arg)
if isinstance(new_object, the_class):
the_class.__init__(new_object, some_arg)
return new_object
使用 shelve 模块调整数据结构
shelve.open()函数可以打开一个数据库文件,返回shelve.Shelf实例,它是简单的键值对象数据库,可使用与字典类似的赋值语句写入数据。使用完后需要调用db.close()方法关闭该对象,或使用with语句块(shelve.Shelf本身也是上下文管理器);Shelf实例通常用于在类 JSON 数据中查找指定的编号数据:先将各数据读入Shelf对象(摆上架子,必须可用pickle处理),赋值键值对(贴标签,必须是字符串),之后就可以通过键来获取对应的数据;- 可以更新实例的
__dict__属性,利用self.__dict__.update(kwargs)来快速创建属性(将字典转化为对象属性)。
使用特性获取链接的记录
- 特性是用于管理实例属性的类属性;
- 小技巧:
- 字符串格式化中的
{}符号支持获取属性:'{0.x}'.format(obj); - 静态方法和类方法的使用场景区别:静态方法用来强调调用的对象与操作内容无关(如操作特定的类属性),类方法用来操作类属性(参数中包含类名);
- 自定义的异常通常是标志类,没有定义体,用文档字符串说明异常用途;
- 注意区分
self.__class__.method()和self.method()两种类方法的调用方式:前者可以避免错误地使用了同名实例方法(实例属性覆盖了类属性); globals().get(cls_name, default_cls)可以用来在当前模块的全局作用域中使用类名获取类对象(提供默认值)。
- 字符串格式化中的
19.2 使用特性验证属性
- Python 中最适合使用特性来替代数据属性,以此实现值的验证,并保持接口不变;
- 使用
@property装饰器装饰读值方法,使用@name.setter装饰器装饰读值方法。真正的值应当存储于对应的私有属性中; - 多次使用类似的特性时,需要考虑使用特性工厂函数(19.4) 或描述符类(第 20 章)。
19.3 特性全解析
property是一个类(可调用对象,返回函数对象,与函数等价)。其构造方法为property(fget=None, fset=None, fsel=None, doc=None),分别对应读值、设置、删除,可以通过显式构建property对象的方式来定义特性:weight = property(get_weight, set_weight);- 特性是一种类属性,它是覆盖型描述符(第 20 章)。在获取实例属性时,会先从类开始,仅当类中没有同名特性时,才会在实例中查找;
- 虽然实例属性会覆盖类属性,但同名的特性不会受到影响,依然会使用特性中指定的属性,只有当特性被销毁后才会把实例属性暴露出来;
- 特性的文档存储在对应的
property对象的__doc__属性中。它可以通过指定读值方法的文档字符串设置,也可以在构造方法中传入doc参数设置; - 小技巧:
vars(obj)函数能返回实例的__dict__属性; - 小技巧:
del关键字的作用实际上是接触变量与值之间的引用关系,因此可以用来销毁各种属性、变量的值。
19.4 定义特性工厂函数
def quantity(storage_name): # <1>
def qty_getter(instance): # <2>
return instance.__dict__[storage_name] # <3> 使用`__dict__`绕开已设置的特性!!
def qty_setter(instance, value): # <4>
if value > 0:
instance.__dict__[storage_name] = value # <5>
else:
raise ValueError('value must be > 0')
return property(qty_getter, qty_setter) # <6>
19.5 处理属性删除操作
@name.deleter装饰器用来装饰删除属性的方法,对应del语句;property构造函数中的fdel对应删除属性的方法;__delattr__特殊方法可以处理一般的属性处理方法。
19.6 处理属性的重要属性和函数
__getattr__和__getattribute__方法的区别:__getattr__仅当在通过常规途径(实例属性、类属性、超类等)获取指定属性失败时使用,而__getattribute__方法则是获取属性的主要方法,会影响每次属性读取。当它抛出AttributeError时调用__getattr__。
Difference between
__getattr__and__getattribute__A key difference between
__getattr__and__getattribute__is that__getattr__is only invoked if the attribute wasn’t found the usual ways. It’s good for implementing a fallback for missing attributes, and is probably the one of two you want.
__getattribute__is invoked before looking at the actual attributes on the object, and so can be tricky to implement correctly. You can end up in infinite recursions very easily.New-style classes derive from
object, old-style classes are those in Python 2.x with no explicit base class. But the distinction between old-style and new-style classes is not the important one when choosing between__getattr__and__getattribute__.You almost certainly want
__getattr__.