对酒当歌,人生几何?譬如朝露,去日苦多。
慨当以慷,忧思难忘。何以解忧?唯有杜康。
——《短歌行 其一》三国时期/曹操
Python之禅 最早由 Tim Peters在Python邮件列表中发表,它包含了影响Python编程语言设计的19条软件编写原则。在最初及后来的一些版本中,一共包含20条,其中第20条是“这一条留空(…)请 Guido 来填写“。这留空的一条从未公布也可能并不存在。
Python之禅作为一个信息条款被录入Python增强建议(PEP)的第20条,在Python语言的官方网站也能找到。它还作为复活节彩蛋被包含在Python解释器中。如果输入 import this 就会在Python的编程环境IDLE中显示。
使用Python的人提倡优美,提倡简洁,能用一行代码实现的功能用五行代码实现就显得尤为丑陋。任何编程语言的高级特征通常都是通过大量的使用经验才发现的,了解这些高级特性有助于我们实现更优美的Python代码段,提升我们的编程效率。下面是 Python 的N种高级特征(黑科技),以及它们的用法。
#1 原生 LRU 缓存 [最低 Python 版本为 3.2]
目前,几乎所有层面上的软件和硬件中都需要缓存。Python 3 将 LRU(最近最少使用算法)缓存作为一个名为「lru_cache」的装饰器,使得对缓存的使用非常简单。
下面是一个简单的斐波那契函数,我们知道使用缓存将有助于该函数的计算,因为它会通过递归多次执行相同的工作。
1 | import time |
现在,我们可以使用「lru_cache」来优化它(这种优化技术被称为「memoization」)。通过这种优化,我们将执行时间从几秒降低到了几纳秒。
1 | from functools import lru_cache |
#2 格式化字符串 f-string [最低 Python 版本为 3.6]
在任何的编程语言中,不使用字符串都是寸步难行的。而为了保持思路清晰,你会希望有一种结构化的方法来处理字符串。大多数使用 Python 的人会偏向于使用「format」方法。
1 | user = "Jane Doe" |
除了「format」,Python 3 还提供了一种通过「f-string」进行字符串插入的灵活方法。使用「f-string」编写的与上面功能相同的代码是这样的:
1 | user = "Jane Doe" |
相比于常见的字符串格式符 %s 或 format 方法,f-strings 直接在占位符中插入变量显得更加方便,也更好理解。
#3 类型提示 Type hinting [最低 Python 版本为 3.5]
静态和动态类型是软件工程中一个热门的话题,几乎每个人 对此有自己的看法。读者应该自己决定何时应该编写何种类型,因此你至少需要知道 Python 3 是支持类型提示的。
1 | def sentence_has_animal(sentence: str) -> bool: |
#4 Lambda 函数
Lambda 函数是一种比较小的匿名函数——匿名是指它实际上没有函数名。
Python 函数通常使用 def a_function_name() 样式来定义,但对于 lambda 函数,我们根本没为它命名。这是因为 lambda 函数的功能是执行某种简单的表达式或运算,而无需完全定义函数。
lambda 函数可以使用任意数量的参数,但表达式只能有一个。
1 | x = lambda a, b : a * b |
我们执行了一些简单的数学运算,而无需定义整个函数。
#5 Map 函数
Map() 是一种内置的 Python 函数,它可以将函数应用于各种数据结构中的元素,如列表或字典。对于这种运算来说,这是一种非常干净而且可读的执行方式。
1 | def square_it_func(a): |
#6 Filter 函数
filter 内置函数与 map 函数非常相似,它也将函数应用于序列结构(列表、元组、字典)。二者的关键区别在于 filter() 将只返回应用函数返回 True 的元素。
详情请看如下示例:
1 | # Our numbers |
我们不仅评估了每个列表元素的 True 或 False,filter() 函数还确保只返回匹配为 True 的元素。非常便于处理检查表达式和构建返回列表这两步。
#7 Itertools 模块
Python 的 Itertools 模块是处理迭代器的工具集合。迭代器是一种可以在 for 循环语句(包括列表、元组和字典)中使用的数据类型。
使用 Itertools 模块中的函数让你可以执行很多迭代器操作,这些操作通常需要多行函数和复杂的列表理解。关于 Itertools 的神奇之处,请看以下示例:
1 | from itertools import * |
#8 Generator 函数
Generator 函数是一个类似迭代器的函数,即它也可以用在 for 循环语句中。这大大简化了你的代码,而且相比简单的 for 循环,它节省了很多内存。
比如,我们想把 1 到 1000 的所有数字相加,以下代码块的第一部分向你展示了如何使用 for 循环来进行这一计算。
如果列表很小,比如 1000 行,计算所需的内存还行。但如果列表巨长,比如十亿浮点数,这样做就会出现问题了。使用这种 for 循环,内存中将出现大量列表,但不是每个人都有无限的 RAM 来存储这么多东西的。Python 中的 range() 函数也是这么干的,它在内存中构建列表。
代码中第二部分展示了使用 Python generator 函数对数字列表求和。generator 函数创建元素,并只在必要时将其存储在内存中,即一次一个。这意味着,如果你要创建十亿浮点数,你只能一次一个地把它们存储在内存中!Python 2.x 中的 xrange() 函数就是使用 generator 来构建列表。
上述例子说明:如果你想为一个很大的范围生成列表,那么就需要使用 generator 函数。如果你的内存有限,比如使用移动设备或边缘计算,使用这一方法尤其重要。
也就是说,如果你想对列表进行多次迭代,并且它足够小,可以放进内存,那最好使用 for 循环或 Python 2.x 中的 range 函数。因为 generator 函数和 xrange 函数将会在你每次访问它们时生成新的列表值,而 Python 2.x range 函数是静态的列表,而且整数已经置于内存中,以便快速访问。
1 | # (1) Using a for loopv |
#9 3个提高工作效率的 Python Collections 容器数据类型
Source code: Lib/collections/init.py
这个模块实现了特定目标的容器,以提供Python标准内建容器 dict , list , set , 和 tuple 的替代选择。
namedtuple() |
创建命名元组子类的工厂函数 |
---|---|
deque |
类似列表(list)的容器,实现了在两端快速添加(append)和弹出(pop) |
ChainMap |
类似字典(dict)的容器类,将多个映射集合到一个视图里面 |
Counter |
字典的子类,提供了可哈希对象的计数功能 |
OrderedDict |
字典的子类,保存了他们被添加的顺序 |
defaultdict |
字典的子类,提供了一个工厂函数,为字典查询提供一个默认值 |
UserDict |
封装了字典对象,简化了字典子类化 |
UserList |
封装了列表对象,简化了列表子类化 |
UserString |
封装了列表对象,简化了字符串子类化 |
在 3.3 版更改: 将 Collections Abstract Base Classes 移到了 collections.abc 模块. 在Python 3.7 为了保持兼容性,他们依然在这个模块可见。最终会被全部移除掉。
Counter
对象
一个计数器工具提供快速和方便的计数。比如
1 | # Tally occurrences of words in a list |
class collections.Counter
([iterable-or-mapping])
一个 Counter
是一个 dict
的子类,用于计数可哈希对象。它是一个集合,元素像字典键(key)一样存储,它们的计数存储为值。计数可以是任何整数值,包括0和负数。 Counter
类有点像其他语言中的 bags或multisets。
元素从一个 iterable 被计数或从其他的 mapping (or counter)初始化:
1 | # a new, empty counter c = Counter() |
Counter对象有一个字典接口,如果引用的键没有任何记录,就返回一个0,而不是弹出一个 KeyError
:
1 | 'eggs', 'ham']) c = Counter([ |
设置一个计数为0不会从计数器中移去一个元素。使用 del
来删除它:
1 | 'sausage'] = 0 # counter entry with a zero count c[ |
计数器对象除了字典方法以外,还提供了三个其他的方法:
elements
()返回一个迭代器,每个元素重复计数的个数。元素顺序是任意的。如果一个元素的计数小于1,
elements()
就会忽略它。1
2
34, b=2, c=0, d=-2) c = Counter(a=
sorted(c.elements())
['a', 'a', 'a', 'a', 'b', 'b']most_common
([n])返回一个列表,提供 n 个频率最高的元素和计数。 如果没提供 n ,或者是
None
,most_common()
返回计数器中的 所有 元素。相等个数的元素顺序随机:1
2'abracadabra').most_common(3) # doctest: +SKIP Counter(
[('a', 5), ('r', 2), ('b', 2)]subtract
([iterable-or-mapping])从 迭代对象 或 映射对象 减去元素。像
dict.update()
但是是减去,而不是替换。输入和输出都可以是0或者负数。1
2
3
4
54, b=2, c=0, d=-2) c = Counter(a=
1, b=2, c=3, d=4) d = Counter(a=
c.subtract(d)
c
Counter({'a': 3, 'b': 0, 'c': -3, 'd': -6})
通常字典方法都可用于 Counter
对象,除了有两个方法工作方式与字典并不相同。
fromkeys
(iterable)这个类方法没有在
Counter
中实现。update
([iterable-or-mapping])从 迭代对象 计数元素或者 从另一个 映射对象 (或计数器) 添加。 像
dict.update()
但是是加上,而不是替换。另外,迭代对象 应该是序列元素,而不是一个(key, value)
对。
注解: 计数器主要是为了表达运行的正的计数而设计;但是,小心不要预先排除负数或者其他类型。为了帮助这些用例,这一节记录了最小范围和类型限制。
Counter
类是一个字典的子类,不限制键和值。值用于表示计数,但你实际上 可以 存储任何其他值。most_common()
方法在值需要排序的时候用。- 原地操作比如
c[key] += 1
, 值类型只需要支持加和减。 所以分数,小数,和十进制都可以用,负值也可以支持。这两个方法update()
和subtract()
的输入和输出也一样支持负数和0。- Multiset多集合方法只为正值的使用情况设计。输入可以是负数或者0,但只输出计数为正的值。没有类型限制,但值类型需要支持加,减和比较操作。
elements()
方法要求正整数计数。忽略0和负数计数。
deque
对象
class collections.deque([iterable[, maxlen]])
返回一个新的双向队列对象,从左到右初始化(用方法 append()) ,从 iterable (迭代对象) 数据创建。如果 iterable 没有指定,新队列为空。
Deque队列是由栈或者queue队列生成的(发音是 “deck”,”double-ended queue”的简称)。Deque 支持线程安全,内存高效添加(append)和弹出(pop),从两端都可以,两个方向的大概开销都是 O(1) 复杂度。
虽然 list 对象也支持类似操作,不过这里优化了定长操作和 pop(0) 和 insert(0, v) 的开销。它们引起 O(n) 内存移动的操作,改变底层数据表达的大小和位置。
如果 maxlen 没有指定或者是 None ,deques 可以增长到任意长度。否则,deque就限定到指定最大长度。一旦限定长度的deque满了,当新项加入时,同样数量的项就从另一端弹出。限定长度deque提供类似Unix filter tail 的功能。它们同样可以用与追踪最近的交换和其他数据池活动。
双向队列(deque)对象支持以下方法:
append(x)
添加 x 到右端。appendleft(x)
添加 x 到左端。clear()
移除所有元素,使其长度为0.copy()
创建一份浅拷贝。count(x)
计算deque中个数等于 x 的元素。extend(iterable)
扩展deque的右侧,通过添加iterable参数中的元素。extendleft(iterable)
扩展deque的左侧,通过添加iterable参数中的元素。注意,左添加时,在结果中iterable参数中的顺序将被反过来添加。index(x[, start[, stop]])
返回第 x 个元素(从 start 开始计算,在 stop 之前)。返回第一个匹配,如果没找到的话,升起 ValueError 。insert(i, x)
在位置 i 插入 x 。如果插入会导致一个限长deque超出长度 maxlen 的话,就升起一个 IndexError 。pop()
移去并且返回一个元素,deque最右侧的那一个。如果没有元素的话,就升起 IndexError 索引错误。popleft()
移去并且返回一个元素,deque最左侧的那一个。如果没有元素的话,就升起 IndexError 索引错误。remove(value)
移去找到的第一个 value。 如果没有的话就升起 ValueError 。reverse()
将deque逆序排列。返回 None 。rotate(n=1)
向右循环移动 n 步。 如果 n 是负数,就向左循环。如果deque不是空的,向右循环移动一步就等价于 d.appendleft(d.pop()) , 向左循环一步就等价于 d.append(d.popleft()) 。
Deque对象同样提供了一个只读属性:
- maxlen
Deque的最大尺寸,如果没有限定的话就是 None 。
除了以上,deque还支持迭代,清洗,len(d), reversed(d), copy.copy(d), copy.deepcopy(d), 成员测试 in 操作符,和下标引用 d[-1] 。索引存取在两端的复杂度是 O(1), 在中间的复杂度比 O(n) 略低。要快速存取,使用list来替代。
Deque从版本3.5开始支持 add(), mul(), 和 imul() 。
1 | from collections import deque |
OrderedDict
对象
class collections.OrderedDict
([items])
返回一个 dict
子类的实例,它具有专门用于重新排列字典顺序的方法。
有序词典就像常规词典一样,但有一些与排序操作相关的额外功能。由于内置的 dict
类获得了记住插入顺序的能力(在 Python 3.7 中保证了这种新行为),它们变得不那么重要了。
一些与 dict
的不同仍然存在:
- 常规的
dict
被设计为非常擅长映射操作。 跟踪插入顺序是次要的。 OrderedDict
旨在擅长重新排序操作。 空间效率、迭代速度和更新操作的性能是次要的。- 算法上,
OrderedDict
可以比dict
更好地处理频繁的重新排序操作。 这使其适用于跟踪最近的访问(例如在 LRU cache 中)。 - 对于
OrderedDict
,相等操作检查匹配顺序。 OrderedDict
类的popitem()
方法有不同的签名。它接受一个可选参数来指定弹出哪个元素。OrderedDict
类有一个move_to_end()
方法,可以有效地将元素移动到任一端。- Python 3.8之前,
dict
缺少__reversed__()
方法。
一些新版本功能:
popitem
(last=True)有序字典的
popitem()
方法移除并返回一个 (key, value) 键值对。 如果 last 值为真,则按 LIFO 后进先出的顺序返回键值对,否则就按 FIFO 先进先出的顺序返回键值对。move_to_end
(key, last=True)将现有 key 移动到有序字典的任一端。 如果 last 为真值(默认)则将元素移至末尾;如果 last 为假值则将元素移至开头。如果 key 不存在则会触发
KeyError
:>>>>>> d = OrderedDict.fromkeys('abcde') >>> d.move_to_end('b') >>> ''.join(d.keys()) 'acdeb' >>> d.move_to_end('b', last=False) >>> ''.join(d.keys()) 'bacde'
相对于通常的映射方法,有序字典还另外提供了逆序迭代的支持,通过 reversed()
。
OrderedDict
之间的相等测试是顺序敏感的,实现为 list(od1.items())==list(od2.items())
。 OrderedDict
对象和其他的 Mapping
的相等测试,是顺序敏感的字典测试。这允许 OrderedDict
替换为任何字典可以使用的场所。
在 3.5 版更改: OrderedDict
的项(item),键(key)和值(value) 视图 现在支持逆序迭代,通过 reversed()
。
在 3.6 版更改: PEP 468 赞成将关键词参数的顺序保留, 通过传递给 OrderedDict
构造器和它的 update()
方法。
OrderedDict
例子和用法
创建记住键值 最后 插入顺序的有序字典变体很简单。 如果新条目覆盖现有条目,则原始插入位置将更改并移至末尾:
1 | class LastUpdatedOrderedDict(OrderedDict): |
一个 OrderedDict
对于实现 functools.lru_cache()
的变体也很有用:
1 | class LRU(OrderedDict): |
Reference
- https://zh.wikipedia.org/wiki/Python%E4%B9%8B%E7%A6%85
- https://towardsdatascience.com/5-advanced-features-of-python-and-how-to-use-them-73bffa373c84
- https://docs.python.org/zh-cn/3/library/collections.html