大约 6 分钟

编写 Python 程序的 10 个典型错误

编写 Python 程序的 10 个典型错误
Photo by Scott Rodgerson / Unsplash

0X00 开头

最近买了本书,叫做《100个Go语言典型错误》,发现这样的总结很有意思。决定自己也写一个,不过以我的水平写本书还是有点离谱了,但是写一篇博客还是没什么问题的,所以就有了这篇文章。

0X01 函数默认值传递空列表

这是一个典型错误,很多很多的 Python 程序员都犯过这个错误。一般在定义一个函数且给它设置默认值的时候我们都会写成 def foo(a=0, b="", c=None) 这个样子,这种写法是完全没有问题的。但是有时候也会写成:def foo(d=[]),这就完犊子了~

我们看下面这段代码

def foo(a=[]):
    a.append('x')
    print(a)

for i in range(10):
    foo()

你以为会输出 10 个 ['x']?那就大错特错了,真正的输出是这样的:

['x']
['x', 'x']
['x', 'x', 'x']
['x', 'x', 'x', 'x']
['x', 'x', 'x', 'x', 'x']
['x', 'x', 'x', 'x', 'x', 'x']
['x', 'x', 'x', 'x', 'x', 'x', 'x']
['x', 'x', 'x', 'x', 'x', 'x', 'x', 'x']
['x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x']
['x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x']

为什么呢?因为函数在定义的时候就将空列表初始化了,并且记录下了引用,以后每次都会对同一个列表进行操作。另外不只是列表,字典作为默认值也会有这个问题的。

0X02 对闭包的无意识理解

很多人对闭包的理解就是粗浅的「函数里定义函数」,当然这是没错的,但是缺少了一些东西。如果只是函数里定义函数的话,下面这段代码会发生什么?

def foo():
    x = 2
    def bar():
        print(x)
    return bar

foo()()

没错,这段代码是会正确输出 2 的,因为闭包还有一个特性就是内部函数能访问外部函数中的变量,即使外部函数已经运行完毕了。

0X03 使用不合适的变量名

这一点虽然很简单,但确实很容易出现,比如用 list / dict / str 做变量名。因为 Python 中这 3 个名字是非常非常常用的type/function,所以万万不可用。

0X04 字符串拼接的性能问题

少量字符串拼接使用 + 当然没有任何问题,但是如果量很大的话就不建议使用 + 了,使用 join 会快非常多。

import time

COUNT = 10000000

# 使用 + 的版本
start_time = time.time()
result1 = ''
for i in range(COUNT):
    result1 += 'x'
end_time = time.time()
plus_time = end_time - start_time

# 使用 join 的版本  
start_time = time.time()
char_list = []
for i in range(COUNT):
    char_list.append('x')
result2 = ''.join(char_list)
end_time = time.time()
join_time = end_time - start_time

print(f"+ 方法耗时: {plus_time:.4f}秒")
print(f"join 方法耗时: {join_time:.4f}秒")

在我的电脑上运行是这样的:

+ 方法耗时: 1.0472秒
join 方法耗时: 0.5272秒

0X05 错误使用字符串的 strip 方法

学过 Python 的肯定都用过字符串的 strip 方法或者它衍生出来的 lstriprstrip,比如我们会用 'hello'.rstrip('o')来去掉字符串右侧的 o

也有些人会用下面这种方法去掉额外的字符:

phone_num = "+8613588888888"
phone_num = phone_num.strip("+86")

因为 strip('o') 可以去掉 o,就自然而然以为 strip('+86') 就是去掉 +86 了。但你实际运行起来就会发现 phone_num 就只剩下 135 了。因为 strip('+86')的意思并不是去掉两侧的 +86 而是去掉两侧的 +/8/6,凑巧这个手机号后面全是 8 所以就全没了 🤷‍♂️

0X06 认为 Python 的多线程没有用

这是一个典型谣言了,很多人一旦听说 Python 有 GIL 之后就大张旗鼓的说:“Python 的多线程屁用没有”。但实际上真的是这样吗?并不是。

Python 的多线程如果用在 CPU 密集型的计算任务上,那确实没什么用;但是如果用在 IO 密集型的任务上,那和真正意义上的多线程是没有显著差别的。所以严谨来说 Python 确实没有真正意义上的多线程,但也不能说 Python 的多线程没有用。

0X07 过分的一行流

有些 Python 程序员推崇精简代码,使用各种列表生成式、字典生成式之类的,这当然是没问题,精简又好看,而且这是非常 Pythonic 的写法。但是有些人有些过分,强行把多行代码挤成一行,或者强行使用 Python 的高级语法。

比如说 a = {v: k for k, v in my_dic.items()} 这种代码,其实挺好的,但是有些人会写这种东西:

result = {f"item_{i}": [x*2 for x in [y+1 for y in range(3)] if x in [z for z in range(10) if z % 2 == 0]] for i in range(3)}

强吗?强,对语言没点理解是写不出来能跑的这种代码的。但是好吗?codereview 的时候可能会被同时打死。

0X08 手撸 csv 文件

是的没错,都 2025 年了还有人在手撸 csv 文件。赶紧了解一下 csv 库的用法吧,不要再去折磨那个逗号和转义符了。

写入:

import csv

# 学生信息数据
students = [
    ['姓名', '年龄', '班级', '成绩'],
    ['张三', 18, '高一(1)班', 85],
    ['李四', 17, '高一(2)班', 92],
    ['王五', 18, '高一(1)班', 78],
    ['赵六', 17, '高一(3)班', 88]
]

# 写入CSV文件
with open('students.csv', 'w', newline='', encoding='utf-8') as file:
    writer = csv.writer(file)
    writer.writerows(students)

print("学生信息已写入 students.csv 文件")

读取:

import csv

# 读取CSV文件
with open('students.csv', 'r', encoding='utf-8') as file:
    reader = csv.reader(file)
    
    print("学生信息列表:")
    for row in reader:
        print(f"{row[0]:<8} {row[1]:<6} {row[2]:<12} {row[3]}")

# 也可以使用字典方式读取
print("\n使用字典方式读取:")
with open('students.csv', 'r', encoding='utf-8') as file:
    reader = csv.DictReader(file)
    
    for row in reader:
        print(f"姓名: {row['姓名']}, 年龄: {row['年龄']}, 班级: {row['班级']}, 成绩: {row['成绩']}")

0X09 文件硬读写

我们有时候会用 open('xxx', 'r').read() 直接把一个文件读到内存里,如果文件比较小确实是可以这样干的,读到内存里直接当字符串处理就好。但是如果文件很大,就不要一直这么搞了,你 1G 的日志文件还通过这种方式读取,服务都要被卡死了。

with open('xxx.log', 'r') as f:
    for line in f:
        pass    

这样我们的 f 就是一个类似生成器的东西了,每次只读一行,就不会再被这么初级的 IO 问题限制性能了。

0X0A 认为 Python 字典是无序的

是的兄弟,2025年了,Python 的字典早就不是无序的了,甚至是 7 年前的 Python 3.7 发布的时候 dict 就不是无序的了。

Python 官方文档贴在这里:

Changed in version 3.7: Dictionary order is guaranteed to be insertion order. This behavior was an implementation detail of CPython from 3.6.

感谢您的支持

如果您喜欢这篇文章,可以通过以下方式支持我:

支付宝
支付宝
微信
微信
TRC20
TRC20