第一部分: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
中出现的顺序相同。
简单来说,三种数据类构造器的共同点和差异如下: