编程学习网 > 编程语言 > Python > 写了10年Python才发现这8个写法差距这么大你天天写的代码可能慢了几十倍
2026
09-27

写了10年Python才发现这8个写法差距这么大你天天写的代码可能慢了几十倍


写Python代码的人经常遇到一个情况。同一个功能有好几种写法。选哪种写法,短时间看不出差别。项目变大以后,差别就出来了。

看一个例子。统计一个列表里每个元素出现的次数。

Python

data = ['a', 'b', 'a', 'c', 'b', 'a']

count = {}

for item in data:

    if item not in count:

        count[item] = 0

    count[item] += 1

这段代码能跑。它有一个小问题。每次循环要判断键是否存在。数据量小的时候无所谓。数据量到百万级,判断次数就是百万次。写法可以改。

Python

count = {}

for item in data:

    count[item] = count.get(item, 0) + 1

用get方法,省掉了if判断。一次循环只调用一次方法。速度有提升。还有更直接的办法。

Python

from collections import Counter

count = Counter(data)

Counter是标准库里的东西。它底层用C实现。处理百万数据比手写循环快很多。很多人不知道这个类。知道了以后,这类统计任务不用再写循环。

 

再看字符串拼接。新手常写这样的代码。

Python

result = ''

for s in string_list:

    result += s

字符串在Python里不可变。每次加号都会创建新字符串。旧字符串被丢弃。内存里反复分配释放。列表有一万个元素,就创建一万个中间字符串。join方法不一样。

Python

result = ''.join(string_list)

join先计算总长度,一次性分配内存。然后逐个复制。中间不产生临时对象。数据量越大,差距越明显。有人测试过,十万个字符串拼接,join比加号快几十倍。这个技巧十年前就有人讲。今天还有人在写加号循环。原因是没有意识到不可变对象的代价。

 

变量交换是另一个常见场景。

Python

a = 1

b = 2

temp = a

a = b

b = temp

Python有更短的写法。

Python

a, b = b, a

右边先构造一个元组。左边按顺序解包。整个过程在字节码层面完成。比手动引入临时变量少几步操作。这个写法不光短。它还避免了临时变量名冲突。代码里少一个变量,就少一个出错的地方。

 

列表推导式用的人多。生成器表达式用的人少。两者长得像。行为差别大。

Python

squares = [x*x for x in range(1000000)]

这行代码立刻创建一个百万元素的列表。内存里实实在在放一百万个整数。换成圆括号。

Python

squares = (x*x for x in range(1000000))

这时候squares是一个生成器。它不预先计算任何值。每次next调用才算一个。内存里只存当前值。处理大数据流的时候,生成器能省下大量内存。有人写循环处理文件行,用列表推导先把所有行读进内存。文件一G以上,机器就卡。改成生成器,内存占用几乎不变。

 

函数默认参数有一个坑。很多人踩过。

Python

def add_item(item, target=[]):

    target.append(item)

    return target

调用add_item(1)返回[1]。再调用add_item(2)返回[1, 2]。第二次调用前没有传target。按理说应该用空列表。实际用了第一次调用修改过的列表。原因是默认参数在函数定义时求值。空列表只创建一次。所有调用共享同一个对象。正确写法用None。

Python

def add_item(item, target=None):

    if target is None:

        target = []

    target.append(item)

    return target

这样每次调用都新建列表。问题消失。这个坑在面试题里出现频率很高。实际项目里也常见。尤其是写工具函数的时候。参数默认值用可变对象,早晚出事。

 

异常处理也有技巧。看这段代码。

Python

try:

    value = data[key]

except KeyError:

    value = default

功能没问题。异常机制有开销。Python里抛异常和捕获异常比普通判断慢。如果key不存在是常见情况,用get更好。

Python

value = data.get(key, default)

get一次调用解决。没有异常开销。如果key不存在是罕见情况,用try更清晰。选择哪种写法,看哪种情况更常发生。这个判断标准很多人不清楚。他们要么全用try,要么全用get。代码性能就差在这些细节上。

 

文件读取也有讲究。

Python

f = open('data.txt')

content = f.read()

f.close()

手动close有风险。中间代码抛异常,close不会执行。文件句柄泄漏。系统资源有限。泄漏多了程序崩溃。with语句解决这个问题。

Python

with open('data.txt') as f:

    content = f.read()

不管有没有异常,文件都会关闭。with后面可以跟多个对象。写日志和读数据同时进行的时候很方便。

Python

with open('input.txt') as fin, open('output.txt', 'w') as fout:

    for line in fin:

        fout.write(line.upper())

两个文件都自动管理。代码缩进清晰。不用记着关哪个。不用处理关闭时的异常。这种写法省心。

 

字典合并以前要写循环。Python 3.5以后可以用解包。

Python

d1 = {'a': 1}

d2 = {'b': 2}

merged = {**d1, **d2}

一行完成。键冲突时后面的覆盖前面的。Python 3.9又加了|运算符。

Python

merged = d1 | d2

效果一样。写法更短。有人担心性能。实测两者差距很小。选哪个看个人习惯。知道有这些写法,读别人代码时不至于看不懂。

 

迭代字典的时候不要改字典。

Python

d = {'a': 1, 'b': 2}

for key in d:

    if d[key] == 1:

        del d[key]

这段代码会抛RuntimeError。字典大小在迭代过程中变了。Python检测到就报错。正确做法是先收集要删的键。

Python

keys_to_delete = [k for k, v in d.items() if v == 1]

for key in keys_to_delete:

    del d[key]

或者直接构造新字典。

Python

d = {k: v for k, v in d.items() if v != 1}

第二种写法更干净。它不改原字典。它创建一个新字典赋给同名变量。旧字典被垃圾回收。代码读起来也顺。过滤逻辑一眼看清。

 

这些技巧有一个共同点。它们都不复杂。单个看都很小。组合起来用,代码质量不一样。写代码的人每天做很多选择。选对了,程序跑得快,维护省力。选错了,问题慢慢积累。等到出问题再改,成本高得多。

以上就是“写了10年Python才发现这8个写法差距这么大你天天写的代码可能慢了几十倍”的详细内容,想要了解更多Python教程欢迎持续关注编程学习网。 

扫码二维码 获取免费视频学习资料

Python编程学习

查 看2022高级编程视频教程免费获取