作为成为一名专业的Python开发人员,不仅仅是要编写可运行的代码,还需要编写简洁、高效、符合 Python 风格的代码。分享几个Python代码技巧。

1. 用 enumerate 获取索引和值

在遍历列表时,我们经常需要同时拿到元素的索引和值。很多人会习惯性地写出 for i in range(len(data)) 这样的代码。

# 不够Pythonic的写法
data = [1, 2, -3, -4]
for i in range(len(data)):
    if data[i] < 0:
        data[i] = 0
        
# data 会变成 [1, 2, 0, 0]

这种写法可以工作,但它不够直观。你需要通过 data[i] 这种间接的方式来访问元素,可读性稍差。

Python内置的 enumerate 函数为此提供了完美的解决方案。它会将一个可迭代对象(如列表)包装成一个枚举对象,在每次迭代时,同时返回索引和对应的值。

# 更好的写法
data = [1, 2, -3, -4]
for idx, num in enumerate(data):
    if num < 0:
        data[idx] = 0
# data 同样会变成 [1, 2, 0, 0]

这样做的好处很明显:代码的意图更加清晰。for idx, num in enumerate(data) 直接告诉读代码的人:“我需要索引和值”。这比 range(len()) 的方式要 Pythonic 得多。

2. 理解 Python 的赋值:别名与副本

这是一个非常基础但极其重要的概念,很多bug的根源就在于此。在Python中,变量赋值(特别是对可变类型如列表、字典)实际上是“贴标签”,而不是“复制内容”。

看下面的例子:

a = [1, 2, 3, 4, 5]
b = a  # 这里没有创建新列表,只是给列表a贴上了一个新标签b

# 修改 b
b[4] = 7

print(f"a 的内存地址: {id(a)}")
print(f"b 的内存地址: {id(b)}")
print(f"a 的内容: {a}") # a 也被改变了

输出结果:

a 的内存地址: 4389792960
b 的内存地址: 4389792960
a 的内容: [1, 2, 3, 4, 7]

id() 函数返回对象的内存地址。可以看到,ab 指向的是同一个内存地址,它们是同一个列表对象的两个名字(别名)。因此,修改 b 就等于修改 a

如果你想创建一个独立的副本,而不是别名,可以使用切片 [:] 或者 .copy() 方法:

a = [1, 2, 3, 4, 5]
b = a.copy() # 或者 b = a[:]
b[4] = 7

print(f"a 的内容: {a}") # a 保持不变
print(f"b 的内容: {b}")

3. 格式化字符串,F-strings 是你的首选

拼接字符串是日常操作。从Python 3.6开始,f-strings(格式化字符串字面量)提供了一种非常简洁和高效的方式。

first_name = "Lily"
age = 18

# 使用 f-string
print(f"Hi, I'm {first_name} and I'm {age} years old!")

相比于老的 .format() 方法或者 % 操作符,f-strings 的可读性更高,因为变量直接嵌入在字符串中,一目了然。而且,它的性能通常也是最好的。

4. 使用生成器节省内存

当你处理大量数据时,内存占用是个不得不考虑的问题。列表推导式会一次性生成所有元素并放入内存,如果数据量巨大,可能会导致内存溢出。

import sys

# 列表推导式,一次性创建所有元素
my_list = [i for i in range(10000)]
print(f"列表占用的内存: {sys.getsizeof(my_list)} bytes")
print(sum(my_list))

# 生成器表达式,按需生成元素
my_gen = (i for i in range(10000))
print(f"生成器占用的内存: {sys.getsizeof(my_gen)} bytes")
print(sum(my_gen))

输出结果可能类似这样(具体数值取决于系统):

列表占用的内存: 85176 bytes
49995000
生成器占用的内存: 104 bytes
49995000

可以看到,生成器本身只占用极小的内存。它是一个“懒加载”的迭代器,只有在你向它请求下一个元素时,它才会去计算和生成这个元素。对于求和、遍历等操作,生成器和列表的行为结果一致,但内存效率天差地别。

但要注意,生成器只能被完整遍历一次。一旦耗尽,它就空了。

my_gen = (i for i in range(3))
print(sum(my_gen)) # 输出 3 (0+1+2)。生成器在此处被耗尽。
print(sum(my_gen)) # 再次求和,输出 0。因为生成器已经空了。
print(list(my_gen)) # 输出 []

这是一个常见的bug来源。

5. 安全地访问字典:.get()

直接用 my_dict['key'] 的方式访问字典,如果键(key)不存在,程序会立即抛出 KeyError 异常并中断。

my_dict = {'item': 'football', 'price': 10.00}
# count = my_dict['count'] 
# 这行代码会引发 KeyError

更稳妥的做法是使用 .get() 方法。它允许你指定一个默认值,当键不存在时,会返回这个默认值,而不是报错。

# 使用 .get()
count = my_dict.get('count', 0) # 如果'count'不存在,返回默认值0
print(count) # 输出 0

这让我们的代码健壮性更强,避免了不必要的 try...except 块。

6. 用 zip 并行遍历多个序列

如果你有两个或多个长度相同的列表,需要将它们的元素一一对应起来处理,zip 函数是最佳选择。

names = ['Amy', 'Ben', 'Clara']
scores = [85, 80, 95]

for name, score in zip(names, scores):
    print(f"{name}: {score}")

输出结果:

Amy: 85
Ben: 80
Clara: 95

zip 会将多个列表像拉链一样合并起来,每次迭代返回一个包含各个列表对应元素的元组。这比使用索引 for i in range(len(names)) 要简洁和清晰得多。

zip 会以最短的序列为准进行配对。

names = ['Amy', 'Ben', 'Clara', 'David'] # 4个元素
scores = [85, 80, 95] # 3个元素
for name, score in zip(names, scores):
    print(f"{name}: {score}") # 只会打印前三对,'David'会被忽略

7. 列表推导式:一行代码实现循环和判断

列表推导式是Python的特色之一,它能用非常简洁的一行代码来创建列表,并且可以结合条件语句,实现筛选和转换。

场景1:筛选元素

假设我们只想保留列表中的偶数:

numbers = [1, 2, 3, 4, 5, 6, 7, 8]

# 传统写法
evens = []
for num in numbers:
    if num % 2 == 0:
        evens.append(num)

# 列表推导式写法
evens_oneline = [num for num in numbers if num % 2 == 0]

print(evens_oneline) # [2, 4, 6, 8]

[num for num in numbers if num % 2 == 0] 这一行代码就完成了循环和判断筛选,非常紧凑。

场景2:条件转换

假设我们想对列表进行处理:如果是偶数,就将它乘以2;如果是奇数,保持不变。

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

# 列表推导式写法
processed = [num * 2 if num % 2 == 0 else num for num in numbers]

print(processed) # [1, 4, 3, 8, 5]

注意,这种带 else 的条件判断要写在 for 循环的前面。

8. 用 collections.defaultdict 简化计数和分组

在统计词频或对数据进行分组时,我们通常需要先检查字典中是否已存在某个键,如果不存在,则需要先初始化一个值(比如0或空列表)。

from collections import defaultdict

words = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple']
# 当访问一个不存在的键时,会自动用int()的返回值(也就是0)来作为默认值
word_count = defaultdict(int) 
for word in words:
    word_count[word] += 1

使用 defaultdict(int) 后,当你第一次访问一个不存在的键时,defaultdict 会自动为你创建一个键,并将其值初始化为 int() 的结果,也就是 0。这样你就可以直接进行 += 1 操作,代码瞬间清爽了不少。

9. 列表去重的高效方法

要从列表中移除重复的元素,最简单、最快速的方法是利用 set 数据结构的特性——元素唯一。

a = [1, 1, 2, 3, 4, 5, 5, 5, 6, 7, 2, 2]
unique_list = list(set(a))
print(unique_list)
# 输出: [1, 2, 3, 4, 5, 6, 7] (顺序可能不同)

需要注意的是,set 是无序的。在Python 3.7及以上版本,set 的实现会保留插入顺序,但在旧版本中顺序会被打乱。如果需要保持原有的顺序,可以使用 dict.fromkeys()

ordered_unique_list = list(dict.fromkeys(a))
print(ordered_unique_list)
# 输出: [1, 2, 3, 4, 5, 6, 7] (顺序保持不变)

10. 拼接字符串的正确姿势:.join()

当需要将一个字符串列表拼接成一个长字符串时,新手可能会用 + 循环相加。这种方式效率很低,因为字符串是不可变对象,每次 += 操作都会创建一个新的字符串对象。

正确且高效的做法是使用字符串的 .join() 方法。

list_of_strings = ["Hello", "my", "friend"]

# 推荐的写法
my_string = " ".join(list_of_strings)
print(my_string) # "Hello my friend"

.join() 方法会一次性计算出最终字符串所需的总长度,然后只进行一次内存分配,效率远高于循环相加。

11. 一行代码反转字典

有时候,我们需要将字典的键和值互换。利用字典推导式,可以一行代码搞定。

dict1 = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
dict2 = {v: k for k, v in dict1.items()}
print(dict2)

输出结果:

{1: 'a', 2: 'b', 3: 'c', 4: 'd'}

这里有一个重要的前提:原始字典中的值(value)必须是唯一的。如果有重复的值,反转后后面的键值对会覆盖前面的,因为字典的键不能重复。


写在最后:工具让开发更简单

掌握这些编码技巧,能让你的Python代码更加地道。当然,高效的开发也离不开顺手的工具。一个好的开发环境可以让你专注于代码逻辑本身,而不是耗费时间在繁琐的安装和配置上。

对于开发者来说,管理不同项目的Python版本是一个常见的麻烦。如果你想简化这个过程,可以了解一下 ServBay。

它支持一键安装Python,并且可以让你在多个版本之间自由切换,这对于维护不同依赖的老项目或尝试新版本特性都非常方便。好的工具能让开发工作事半功倍。

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:[email protected]