数据的属性和处理数据的方法统称属性,方法只是可调用的属性。还可以创建特性,使用存取方法修改数据属性。

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__.