Python风格对象
目录
内容概要:
- 如何使用特殊方法和约定的结构,定义行为良好且符合 Python 风格的类
- 符合 Python 风格的对象应该正好符合所需,而不是堆砌语言特性
1. 对象表示形式
|特殊方法|调用函数|作用| |: —|: —|: —| |__str__|str()|以用户理解的方式返回对象的字符串表示形式| |__repr__|repr()|以开发者理解的方式返回对象的字符串表示形式| |__bytes__|bytes()|获取对象的字节序列表示形式| |__format__|format() str.format()|使用特殊的格式代码显示对象的字符串表示形式| |__int__ |int()|在某些情况下用于强制转换类型| |__float__|float()|在某些情况下用于强制转换类型| |__complex__|complex()|对象的复数形式|
Python 3:
- __repr__、 __str__ 和 __format__ 都必须返回 Unicode 字符串(str 类型)
- __bytes__ 方法应该返回字节序列(bytes 类型)
__index__:
- 作用: 强制把对象转换成整数索引
- 应用:
- 特定的序列切片场景中使用,以及满足 NumPy 的一个需求
- 在实际编程中,不用实现 __index__ 方法,除非决定新建一种数值类型, 并想把它作为参数传给 __getitem__ 方法
- 参考:
- What’s New in Python 2.5 https://docs.python.org/2.5/whatsnew/pep-357.html
- PEP 357—Allowing Any Object to be Used for Slicing https://www.python.org/dev/peps/pep-0357/
2. 类的两个装饰器
classmethod:
- 作用: 定义操作类,而不是操作实例的方法
- 参数: 类方法的第一个参数是类本身,而不是实例
- 用途: 最常见的用途是定义备选构造方法
staticmethod:
- 作用: 静态方法就是普通的函数,在类的定义体中,而不是在模块层定义
- 用法: The Definitive Guide on How to Use Static, Class or Abstract Methods inPython https://julien.danjou.info/blog/2013/guide-python-static-class-abstract-methods)
3. 字符串格式化
格式字符串句法:
- 作用: 字符串格式化使用的语法,又称代换字段表示法
- 文档: Format String Syntax https://docs.python.org/3/library/string.html#formatspec
- 语法: {字段名称: 格式说明符}
- 字段名称: 与格式说明符无关,用于决定把 .format() 的哪个参数传给代换字段
- 格式说明符: 使用的表示法叫格式规范微语言(Format Specification Mini-Language)
- 附注:
- format() 函数,只使用格式规范微语言
- str.format() 使用格式字符串句法
格式规范微语言
- 文档: https://docs.python.org/3/library/string.html#formatspec
- 特性:
- 为一些内置类型提供了专用的表示代码
- 浮点数使用的格式代码 ’eEfFgGn%’, f 表示 float 类型,% 表示百分数形式
- 整数使用的格式代码有 ‘bcdoxXn’,b 和 x 分别表示二进制和十六进制的 int 类型
- 字符串使用的是 ’s’
- 是可扩展的,方法是实现 __format__ 方法,对提供给内置函数 format(obj, format_spec) 的 format_spec,或者提供给 str.format 方法的 ‘{: «format_spec»}’ 位于代换字段中的 «format_spec» 做简单的解析
- 为一些内置类型提供了专用的表示代码
|
|
4. 可散列对象
需实现方法:
- __hash__:
- 实现:
- 应该返回一个整数
- 还要考虑对象属性的散列值,因为相等的对象应该具有相同的散列值
- 最好使用位运算符异或( ^)混合各分量的散列值
- 文档: https://docs.python.org/3/reference/datamodel.html
- __eq__: 检测相等性,若 a == b 为真,则 hash(a) == hash(b) 也为真
- 对象不可变:
- 实现: 要想创建可散列的类型,不一定要实现特性,也不一定要保护实例属性。只 需正确地实现 __hash__ 和 __eq__ 方法即可。但是,实例的散列值绝不应该变 化以保证散列值不可变,因此需要实现只读特性保证对象不可变
- 方法:
- 将属性值保存在私有属性中,再以只读特性公开
- 使用 @property 装饰器把读取私有属性的读值方法标记为特性,读值方法与公开属性同名
私有属性
- 定义: 两个前导下划线,尾部没有或最多有一个下划线命名的实例属性
- 特性:
- Python 会把属性名存入实例的 __dict__ 属性中,而且会在前面加上一个下划线和类名
- 又称为名称改写,eg: __mood 会变成 _Dog__mood
- 目的: 避免子类意外覆盖“私有”属性,不能防止故意做错事
- 附注: Python 解释器不会对使用单个下划线的属性名做特殊处理
5. __slots__
实例属性:
- 默认 Python 在实例中名为 __dict__ 的字典里存储实例属性
- __slots__: 让解释器在元组中存储实例属性,而不用字典
- 继承自超类的 __slots__ 属性没有效果,Python 只会使用各个类中定义的 slots 属性
__slots__:
- 语法: 创建一个类属性 __slots__ eg:
__slots__ = ('__x', '__y')
- 类型: 值为一个字符串构成的可迭代对象,其中各个元素表示各个实例属性
- 作用: 告诉解释器,这个类中的所有实例属性都在这儿了,Python 会在各个实例中使用类似元组的 结构存储实例变量,从而避免使用消耗内存的 __dict__ 属性
- 特性:
- 定义 __slots__ 属性之后,实例不能再有 __slots__ 中所列名称之外的其他属性
- 如果把 __dict__ 添加到 __slots__中,实例会在元组中保存各个实例的属性, 此外还支持动态创建属性,这些属性存储在常规的 __dict__ 中
- 把 ‘__dict__’ 添加到 __slots__ 中可能完全违背了初衷,这取决于各个实例的 静态属性和动态属性的数量及其用法
- 应用:
- 处理列表数据时 __slots__ 属性最有用,例如模式固定的数据库记录,以及特大型数据集
- 如果要处理数百万个数值对象,应该使用 NumPy 数组
- NumPy 数组能高效使用内存,而且提供了高度优化的数值处理函数,其中很多都一次操作整个数组
__slots__问题:
- 每个子类都要定义 __slots__ 属性,因为解释器会忽略继承的 __slots__ 属性
- 实例只能拥有 __slots__ 中列出的属性,除非把 ‘__dict__’ 加入 __slots__ 中
- 如果不把 ‘__weakref__’ 加入 __slots__,实例就不能作为弱引用的目标
- 不要使用 __slots__ 属性禁止类的用户新增实例属性,__slots__ 是用于优化的,不是为了约束程序员
- 仅当权衡当下的需求并仔细搜集资料后证明确实有必要时,才应该使用 __slots__ 属性
__weakref__:
- 为了让对象支持弱引用,必须有这个属性
- 用户定义的类中默认就有 __weakref__ 属性
- 如果类中定义了 __slots__ 属性,而且想把实例作为弱引用的目标, 那么要把 ‘__weakref__‘添加到 __slots__ 中
6. 符合 Python 风格的对象
6.1 __slots__
|
|
6.2 可散列与公开只读属性
|
|
6.3 对象表示形式
|
|
6.4 自定义格式代码
|
|
__format__
- 如果类没有定义 __format__ 方法
- 会从 object 继承的方法会返回 str(my_object)
- 如果传入格式说明符, object.__format__ 方法会抛出 TypeError
|
|
6.5 备选构造方法
|
|
7. 覆盖类属性
- 类属性可用于为实例属性提供默认值
- 实例属性会覆盖同名类属性
- 类属性是公开的,因此会被子类继承,可以通过创建子类,用于定制类属性
延伸阅读
Python:
NumPy:
Pandas:
blog:
实用工具
书籍:
Python 语言参考手册中
- “ Data Model” 一章 https://docs.python.org/3/reference/datamodel.html
- 3.3.1. Basic customization” https://docs.python.org/3/reference/datamodel.html#basic-customization
《 Python 技术手册(第 2 版)》
《 Python Cookbook(第 3 版)中文版》
《 Python 参考手册(第 4 版)》
附注
特性:
- 可以先以最简单的方式定义类,也就是使用公开属性
- 如果以后需要对读值方法和设值方法增加控制,那就可以实现特性
- 这样做对一开始通过公开属性的名称与对象交互的代码没有影响
- Java:
- 没有特性
- API 不能从简单的公开属性变成读值方法和设值方法,同时又不影响使用那些属性的代码
私有属性的安全性和保障性
- Java 的 private 和 protected 修饰符往往只是为了防止意外 (即一种安全措施)。只有使用安全管理器部署应用时才能保障绝对安全,防止恶意访 问;但是,实际上很少有人这么做,即便在企业中也少见
|
|