No history yet

Pythonic容器技巧

更 Pythonic 的推导式

你可能已经习惯了使用循环来填充列表或字典。这很有效,但不够“Pythonic”。Python 提供了一种更简洁、通常也更高效的方式:推导式 (Comprehensions)。

想象一下,你想创建一个包含 0 到 9 的平方数的列表。传统的做法是这样的:

squares = []
for i in range(10):
    squares.append(i**2)
# squares -> [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

而使用列表推导式,你可以将这三行代码压缩成一行:

squares = [i**2 for i in range(10)]
# squares -> [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

这不仅仅是代码变短了。因为推导式在 C 语言层面进行了优化,它的执行速度通常比显式的 for 循环更快。你还可以在推导式中加入条件判断。例如,只计算偶数的平方:

even_squares = [i**2 for i in range(10) if i % 2 == 0]
# even_squares -> [0, 4, 16, 36, 64]

同样的概念也适用于字典和集合。字典推导式允许你用一行代码从一个可迭代对象中创建字典。

# 创建一个将数字映射到其平方的字典
number_map = {x: x**2 for x in (2, 4, 6)}
# number_map -> {2: 4, 4: 16, 6: 36}

精通切片

切片 (Slicing) 不仅仅是提取列表的一部分。它的完整语法是 [start:stop:step],这为我们提供了强大的数据操控能力。

step 参数控制切片的步长。如果省略,默认为 1。如果设置为 2,则表示每隔一个元素取一个。如果设置为 -1,则可以优雅地反转列表。

letters = ['a', 'b', 'c', 'd', 'e', 'f']

# 反转列表
reversed_letters = letters[::-1]
# reversed_letters -> ['f', 'e', 'd', 'c', 'b', 'a']

# 获取所有偶数索引位置的元素
even_index_letters = letters[::2]
# even_index_letters -> ['a', 'c', 'e']

一个更高级的用法是使用切片进行赋值,这可以一次性替换、删除或插入多个列表元素。

numbers = [1, 2, 3, 4, 5]

# 用两个新元素替换索引 1 和 2 的元素
numbers[1:3] = [8, 9]
# numbers -> [1, 8, 9, 4, 5]

# 在列表的开头插入元素(不推荐,性能较低)
numbers[:0] = [-1, 0]
# numbers -> [-1, 0, 1, 8, 9, 4, 5]

重要提示:对列表进行切片会创建一个新的列表,这是一个“浅拷贝”。这意味着新列表中的元素是原列表中元素的引用。如果元素是可变对象(如列表或字典),修改其中一个会影响另一个。

高性能容器

Python 的标准库 collections 模块提供了一些内置容器的替代品,它们为特定用例提供了更高的性能。

namedtuple

元组 (tuple) 是不可变的,并且比列表更节省内存。但通过索引访问元组元素(如 data[0])会降低代码的可读性。namedtuple 通过为元组中的每个位置分配名称来解决这个问题,让你像访问对象属性一样访问它们。

from collections import namedtuple

# 定义一个名为 'Point' 的 namedtuple 类型
Point = namedtuple('Point', ['x', 'y'])

p = Point(10, 20)

# 通过名称访问,更具可读性
print(p.x) # -> 10
print(p.y) # -> 20

deque

deque(发音为“deck”)是“双端队列”的缩写。它与列表类似,但对在两端添加(append)和弹出(pop)元素进行了优化。对于列表,在开头插入或删除元素(如 list.insert(0, ...)list.pop(0))是一个 O(n)O(n) 操作,因为所有后续元素都需要移动位置。而对于 deque,这些操作是 O(1)O(1) 的,速度非常快。

from collections import deque

d = deque(['b', 'c', 'd'])

# 在左侧高效添加元素
d.appendleft('a')

# 在右侧高效添加元素
d.append('e')

print(d) # -> deque(['a', 'b', 'c', 'd', 'e'])

# 从左侧高效弹出元素
d.popleft()
print(d) # -> deque(['b', 'c', 'd', 'e'])

当你需要实现队列(先进先出)或需要频繁在序列的两端进行操作时,deque 是不二之选。

哈希与集合

你有没有想过为什么在字典或集合中检查一个元素是否存在(key in my_dict)会如此之快,即使数据量巨大?答案是哈希。

字典和集合在内部都使用了哈希表。当你向集合中添加一个元素时,Python 会对该元素调用 hash() 函数,得到一个整数,即哈希值。这个哈希值被用来计算元素在内存中的存储位置。这样,当你要查找一个元素时,Python 可以再次计算它的哈希值,并直接跳转到对应的内存位置,而无需逐个比较。

hashable

adjective

一个对象如果拥有一个在其生命周期内永不改变的哈希值,并且可以与其他对象进行比较,那么它就是可哈希的。Python中所有不可变的内置类型(如字符串、数字、元组)都是可哈希的,而可变类型(如列表、字典)则不是。

由于集合是基于哈希表实现的,它们提供了一些非常高效的操作,用于处理数据集之间的关系。这些操作的灵感来自于数学中的集合论。

set_a = {1, 2, 3, 4}
set_b = {3, 4, 5, 6}

# 并集 (Union)
print(set_a | set_b)  # -> {1, 2, 3, 4, 5, 6}

# 交集 (Intersection)
print(set_a & set_b)  # -> {3, 4}

# 差集 (Difference)
print(set_a - set_b)  # -> {1, 2}

# 对称差集 (Symmetric Difference)
print(set_a ^ set_b)  # -> {1, 2, 5, 6}

当需要快速去重或执行成员资格测试时,使用集合通常比列表高效得多。

Quiz Questions 1/5

以下哪项列表推导式与下面的代码片段等效?

squares = []
for i in range(5):
    if i % 2 == 0:
        squares.append(i * i)
Quiz Questions 2/5

运行以下 Python 代码会输出什么?

my_list = [1, 2, 3, 4, 5]
print(my_list[::-2])

掌握这些技巧能让你的代码更简洁、更高效,也更能体现 Python 语言的精髓。