本文分享 11 个实用的 Python 代码技巧,包括使用 enumerate、理解赋值机制、F-strings、生成器、dict.get()、zip()、列表推导式、defaultdict、列表去重和 str.join() 等,帮助你编写简洁高效的 Python 代码。
enumerate()
获取索引和值在遍历列表时,我们经常需要同时拿到元素的索引和值。很多人会习惯性地写出
for i in range(len(my_list))
print(i, my_list[i])
这种写法可以工作,但它不够直观。你需要通过 my_list[i]
这种间接的方式来访问元素,可读性稍差。
Python 内置的 enumerate()
函数为此提供了完美的解决方案。它会将一个可迭代对象 (如列表) 包装成一个枚举对象,在每次迭代时,同时返回索引和对应的值。
my_list = ["A", "B", "C"]
for index, value in enumerate(my_list):
print(index, value)
0 A 1 B 2 C
这样做的好处很明显:代码的意图更加清晰。enumerate()
直接告诉读代码的人:“我需要索引和值”。这比 range(len(my_list))
的方式要 Pythonic 得多。
这是一个非常基础但极其重要的概念,很多 bug 的根源就在于此。在 Python 中,变量赋值 (特别是对可变类型如列表、字典) 实际上是“贴标签”,而不是“复制内容”。
看下面的例子:
a = [1, 2, 3]
b = a
b.append(4)
print(a)
print(id(a), id(b))
[1, 2, 3, 4] 4452752320 4452752320
输出结果:[1, 2, 3, 4]
以及两个相同的内存地址。id()
函数返回对象的内存地址。可以看到,a
和 b
指向的是同一个内存地址,它们是同一个列表对象的两个名字 (别名)。因此,修改 b
就等于修改 a
。
如果你想创建一个独立的副本,而不是别名,可以使用切片 a[:]
或者 a.copy()
方法:
a = [1, 2, 3]
b = a.copy() # 或者 b = a[:]
b.append(4)
print(a)
[1, 2, 3]
拼接字符串是日常操作。从 Python 3.6 开始,f-strings (格式化字符串字面量) 提供了一种非常简洁和高效的方式。
name = "Alice"
age = 30
print(f"Hello, my name is {name} and I'm {age} years old.")
Hello, my name is Alice and I'm 30 years old.
相比于老的 str.format()
方法或者 %
操作符,f-strings 的可读性更高,因为变量直接嵌入在字符串中,一目了然。而且,它的性能通常也是最好的。
当你处理大量数据时,内存占用是个不得不考虑的问题。列表推导式会一次性生成所有元素并放入内存,如果数据量巨大,可能会导致内存溢出。
生成器表达式则不同,它的语法和列表推导式类似,只是把方括号 []
换成了圆括号 ()
:
# 列表推导式:创建完整列表,占用内存大
large_list = [x * 2 for x in range(10**6)]
print(large_list.__sizeof__()) # 输出列表占用的字节数 (很大)
# 生成器表达式:不立即生成所有元素,内存占用小
generator = (x * 2 for x in range(10**6))
print(generator.__sizeof__()) # 输出生成器对象本身占用的字节数 (很小)
8448712 184
输出结果可能类似这样 (具体数值取决于系统):8448712
(列表) 和 184
(生成器)。可以看到,生成器本身只占用极小的内存。它是一个懒加载的迭代器,只有在你向它请求下一个元素时,它才会去计算和生成这个元素。对于求和、遍历等操作,生成器和列表的行为结果一致,但内存效率天差地别。
但要注意,生成器只能被完整遍历一次。一旦耗尽,它就空了。
g = (x for x in range(3))
print(list(g)) # [0, 1, 2]
print(list(g)) # [] (已经耗尽)
[0, 1, 2] []
这是一个常见的 bug 来源。
dict.get()
直接用 d[key]
的方式访问字典,如果键 (key) 不存在,程序会立即抛出 KeyError
异常并中断。
更稳妥的做法是使用 dict.get(key, default)
方法。它允许你指定一个默认值,当键不存在时,会返回这个默认值,而不是报错。
person = {"name": "Alice", "age": 30}
# 安全获取 'city',不存在则返回 'Unknown'
city = person.get("city", "Unknown")
print(city)
Unknown
这让我们的代码健壮性更强,避免了不必要的 try-except
块。
zip()
并行遍历多个序列如果你有两个或多个长度相同的列表,需要将它们的元素一一对应起来处理,zip()
函数是最佳选择。
names = ["Alice", "Bob", "Charlie"]
ages = [30, 25, 35]
for name, age in zip(names, ages):
print(f"{name} is {age} years old.")
Alice is 30 years old. Bob is 25 years old. Charlie is 35 years old.
zip()
会将多个列表像拉链一样合并起来,每次迭代返回一个包含各个列表对应元素的元组。这比使用索引 for i in range(len(names)): print(names[i], ages[i])
要简洁和清晰得多。
zip()
会以最短的序列为准进行配对。
列表推导式是 Python 的特色之一,它能用非常简洁的一行代码来创建列表,并且可以结合条件语句,实现筛选和转换。
假设我们只想保留列表中的偶数:
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even_numbers = [x for x in numbers if x % 2 == 0]
print(even_numbers)
[2, 4, 6, 8, 10]
[x for x in numbers if x% 2 == 0]
这一行代码就完成了循环和判断筛选,非常紧凑。
假设我们想对列表进行处理:如果是偶数,就将它乘以 2;如果是奇数,保持不变。
numbers = [1, 2, 3, 4, 5]
processed = [x * 2 if x % 2 == 0 else x for x in numbers]
print(processed)
[1, 4, 3, 8, 5]
注意,这种带 else
的条件判断要写在 for x in numbers
循环的前面。
collections.defaultdict
简化计数和分组在统计词频或对数据进行分组时,我们通常需要先检查字典中是否已存在某个键,如果不存在,则需要先初始化一个值 (比如 0 或空列表)。
words = "a b c a a b".split(" ")
# 普通字典的繁琐写法
word_counts = {}
for word in words:
if word not in word_counts:
word_counts[word] = 0 # 初始化
word_counts[word] += 1
word_counts
{'a': 3, 'b': 2, 'c': 1}
使用 collections.defaultdict
后,当你第一次访问一个不存在的键时,defaultdict
会自动为你创建一个键,并将其值初始化为 default_factory
的结果,也就是 int()
(即 0)。这样你就可以直接进行 += 1
操作,代码瞬间清爽了不少。
from collections import defaultdict
word_counts = defaultdict(int) # 键不存在时自动初始化为 0
for word in words:
word_counts[word] += 1 # 无需手动初始化
print(word_counts)
# 分组示例 (初始化为空列表)
items = [("A", "Apple"), ("B", "Banana"), ("B", "Blueberry")]
groups = defaultdict(list)
for key, value in items:
groups[key].append(value) # 无需手动初始化列表
print(groups)
defaultdict(<class'int'>, {'a': 3, 'b': 2, 'c': 1}) defaultdict(<class'list'>, {'A': ['Apple'], 'B': ['Banana', 'Blueberry']})
要从列表中移除重复的元素,最简单、最快速的方法是利用 set
数据结构的特性——元素唯一。
numbers = [1, 2, 2, 3, 4, 4, 5]
unique_numbers = list(set(numbers))
print(unique_numbers)
[1, 2, 3, 4, 5]
需要注意的是,set
是无序的。在 Python 3.7 及以上版本,dict
的实现会保留插入顺序,但在旧版本中顺序会被打乱。如果需要保持原有的顺序,可以使用 dict.fromkeys()
:
numbers = [1, 2, 2, 3, 4, 4, 5]
unique_numbers = list(dict.fromkeys(numbers)) # 利用 dict 键的唯一性和插入顺序
print(unique_numbers)
[1, 2, 3, 4, 5]
str.join()
当需要将一个字符串列表拼接成一个长字符串时,新手可能会用 for
循环相加。这种方式效率很低,因为字符串是不可变对象,每次 +=
操作都会创建一个新的字符串对象。
# 低效的方式
words = ["Hello", "world", "!"]
result = ""
for word in words:
result += word # 每次都创建新字符串
正确且高效的做法是使用字符串的 join()
方法。
words = ["Hello", "world", "!"]
result = " ".join(words) # 用空格连接
print(result)
Hello world!
join()
方法会一次性计算出最终字符串所需的总长度,然后只进行一次内存分配,效率远高于循环相加。
有时候,我们需要将字典的键和值互换。利用字典推导式,可以一行代码搞定。
original = {"a": 1, "b": 2, "c": 3}
reversed_dict = {v: k for k, v in original.items()}
print(reversed_dict)
{1: 'a', 2: 'b', 3: 'c'}
这里有一个重要的前提:原始字典中的值 (value) 必须是唯一的。如果有重复的值,反转后后面的键值对会覆盖前面的,因为字典的键不能重复。
original = {"a": 1, "b": 1}
reversed_dict = {v: k for k, v in original.items()}
print(reversed_dict)
{1: 'b'}