第一部分:Python 数据结构¶
约 1421 个字 82 行代码 2 张图片 预计阅读时间 6 分钟
一、Python 数据模型¶
Python 解释器通过调用特殊方法来执行对象基本操作,特殊方法的名称两端都有双下划线,比如我们在使用 [] 来访问元素的时候,其实背后提供支持的是 __getitem__ 方法,运算符重载的实现就是通过重写这些特殊方法。
Code
下面是 Python 特殊方法的列表:
Python 特殊方法
首先是不包含运算符的特殊方法:
- 字符串(字节)表示方式:
__repr__、__str__、__format__、__bytes__、__fspath__; - 数值转换:
__bool__、__complex__、__int__、__float__、__hash__、__index__; - 模拟容器:
__len__、__getitem__、__setitem__、__delitem__、__contains__; - 迭代:
__iter__、__aiter__、__next__、__anext__、__reversed__; - 可调用对象:
__call__、__await__; - 上下文管理:
__enter__、__exit__、__aexit__、__aenter__; - 实例创建和销毁:
__new__、__init__、__del__; - 属性管理:
__getattr__、__getattribute__、__setattr__、__delattr__、__dir__; - 属性描述符:
__get__、__set__、__delete__、__set_name__; - 抽象基类:
__instancecheck__、__subclasscheck__; - 类元编程:
__prepare__、__init_subclass__、__class_getitem__、__mro_entries__;
下面是包含运算符的特殊方法:
- 一元数值运算:
__neg__(-)、__pos__(+)、__abs__(abs()); - 比较运算:
__lt__(<)、__le__(<=)、__eq__(==)、__ne__(!=)、__gt__(>)、__ge__(>=); - 算术运算:
__add__(+)、__sub__(-)、__mul__(*)、__truediv__(/)、__floordiv__(//)、__mod__(%)、__matmul__(@)、__divmod__(divmod())、__round__(round())、__pow__(**); - 反向算术运算符(交换操作数):
__radd__、__rsub__、__rmul__、__rtruediv__、__rfloordiv__、__rmod__、__rmatmul__、__rdivmod__、__rround__、__rpow__; - 增量赋值算术运算符:
__iadd__(+=)、__isub__(-=)、__imul__(*=)、__itruediv__(/=)、__ifloordiv__(//=)、__imod__(%=)、__imatmul__(@=)、__ipow__(**=); - 位运算:
__and__(&)、__or__(|)、__xor__(^)、__lshift__(<<)、__rshift__(>>)、__invert__(~); - 反向位运算符(交换操作数):
__rand__、__ror__、__rxor__、__rlshift__、__rrshift__; - 增强赋值位运算符:
__iand__(&=)、__ior__(|=)、__ixor__(^=)、__ilshift__(<<=)、__irshift__(>>=);
对于 __add__ 方法以及 __radd__ 方法,如果第一个操作数对应的特殊方法不可用,则 Python 会尝试调用第二个操作数的 __radd__ 方法。
Python 对象基本都需要一个可用的字符串表示方法,这在调试和交互时非常有用。__repr__ 方法供内置函数 repr() 使用,获取对象的字符串表示形式,交互式控制台和调试器在表达式求值结果上就调用 repr() 函数。我们要求 __repr__ 方法返回的字符串应当没有歧义。__str__ 方法供内置函数 str() 使用,在背后供 print() 函数使用,有时候 __repr__ 方法返回的字符串足够好,就无需定义 __str__ 方法,因为继承自 object 的 __str__ 方法最终会调用 __repr__。
默认情况下,用户定义的实例都是真值,除非实现了 __bool__ 方法或者 __len__ 方法。简单来说,bool(x) 会调用 x.__bool__(),并且以 __bool__ 方法的返回值为准,如果没有实现 __bool__ 方法,则尝试调用 __len__ 方法,如果 __len__ 方法返回 0,则 bool(x) 返回 False,否则返回 True。我们要求 __bool__ 方法必须返回布尔值。
下面这张图展示了 Python 的基本容器类型的接口,图中所有的都是抽象基类:

顶部三个抽象基类都只有一个特殊方法,每一个容器类型都需要实现如下事项:
- Iterable 要支持
for、拆包和其他迭代方式; - Sized 要支持内置函数
len(); - Container 要支持
in运算符;
Python 不强制要求具体类继承这些抽象基类中的任何一个,只需要实现对应的特殊方法,就说明对应的类满足对应接口。
Collection 有三个很重要的接口:
- Sequence 规范 list 和 str 等内置类型的接口;
- Mapping 被 dict、collections.defaultdict 等实现;
- Set 规范 set、frozenset 等内置类型的接口;
并且只有 Sequence 需要实现 __reversed__ 方法,因为需要按照各种方向排列内容。
二、序列¶
2.1 列表推导式与生成器表达式¶
2.2 序列与可迭代对象拆包¶
2.3 序列模式匹配¶
2.4 切片¶
2.5 +、*、list.sort() 和 sorted()¶
三、字典和集合¶
3.x 可哈希¶
可哈希/Hashable 在 Python 的术语表中有定义,其含义如下:如果一个对象的哈希值在整个生命周期内不改变,并且这个对象可以与其他对象相比较,那么这个对象就是可哈希的,两个可哈希对象相等当且仅当其哈希值相等。可以见的,我们需要实现 __hash__ 方法和 __eq__ 方法。
数值类型和不可变的扁平类型 str、bytes 都是可哈希的;如果容器类型是不可变的,并且其所含的对象都是可哈希的,那么容器自身是可哈希的。
一个对象的哈希值根据所使用的 Python 版本和设备架构有关,哈希计算过程中也有加盐操作,正确实现的对象,其哈希值在一个进程内保持不变。
五、数据类构建器¶
Python 提供了几种构建简单类的方式,这些类只是字段的容器,几乎没有额外功能,这些模式被称为数据类/Data Class,dataclasses 包就支持该模式。这一节介绍 collections.namedtuple、typing.NamedTuple 和 @dataclasses.dataclass。
5.1 概述¶
例子
可以看见,这个类的实现比较简单但是失败:
- 没有
__repr__方法,从object继承的__repr__方法返回的字符串没有信息量; - 没有
__eq__方法,从object继承的__eq__方法比较的是对象的地址/ID,而不是lat和lon的值; - 想要对比两个坐标是否相等,需要手动对比
lat和lon的值;
typing.NamedTuple 可以在 class 语句最后使用,类型注释按照 PEP 526 标准编写,这样的代码更易读。值得注意的是,虽然 typing.NamedTuple 位于超类的位置上,但是实际上 typing.NamedTuple 使用元类构建用户类。typing.NamedTuple 生成的 __init__ 方法中,字段参数的顺序与在 class 中出现的顺序相同。
简单来说,三种数据类构造器的共同点和差异如下:
