引言
在 Python 开发中,代码的可读性、可维护性和性能往往比单纯的运行速度更重要。许多初学者或经验不足的开发者容易养成一些不符合 Pythonic 风格的编程习惯,这些习惯不仅会降低代码质量,还可能给团队协作带来隐患。通过识别并摒弃这些坏习惯,采用更地道的 Python 写法,可以显著提升你的代码水平。
本文总结了 Python 开发中常见的 18 个坏习惯,涵盖字符串拼接、资源管理、异常处理、可变默认参数、推导式使用、类型检查、日志记录及 subprocess 调用等方面。针对每个问题提供了具体的反模式示例和符合 Pythonic 风格的改进方案,并补充了 Black、Pylint 等自动化工具的使用建议,旨在帮助开发者提升代码可读性、安全性和维护性。

在 Python 开发中,代码的可读性、可维护性和性能往往比单纯的运行速度更重要。许多初学者或经验不足的开发者容易养成一些不符合 Pythonic 风格的编程习惯,这些习惯不仅会降低代码质量,还可能给团队协作带来隐患。通过识别并摒弃这些坏习惯,采用更地道的 Python 写法,可以显著提升你的代码水平。
本文将详细列出 18 个常见的 Python 坏习惯,并提供对应的最佳实践方案。同时,我们还将介绍如何利用自动化工具来辅助养成良好的编码习惯。
坏的做法:
使用 + 号拼接字符串不仅效率低,而且可读性差,尤其是在处理多个变量时。
def manual_str_formatting(name, subscribers):
if subscribers > 100000:
print("Wow " + name + "! you have " + str(subscribers) + " subscribers!")
else:
print("Lol " + name + " that's not many subs")
好的做法: 使用 f-string(格式化字符串字面量),它更高效且语法清晰。
def manual_str_formatting(name, subscribers):
# better
if subscribers > 100000:
print(f"Wow {name}! you have {subscribers} subscribers!")
else:
print(f"Lol {name} that's not many subs")
坏的做法: 手动管理资源需要显式调用关闭方法,如果发生异常可能导致资源未释放。
def finally_instead_of_context_manager(host, port):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect((host, port))
s.sendall(b'Hello, world')
finally:
s.close()
好的做法:
使用上下文管理器 (with 语句),即使发生异常,资源也会自动关闭。
def finally_instead_of_context_manager(host, port):
# close even if exception
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((host, port))
s.sendall(b'Hello, world')
坏的做法:
显式调用 close() 容易遗漏,特别是在复杂逻辑分支中。
def manually_calling_close_on_a_file(filename):
f = open(filename, "w")
f.write("hello!\n")
f.close()
好的做法: 始终优先使用上下文管理器,确保文件自动关闭。
def manually_calling_close_on_a_file(filename):
with open(filename) as f:
f.write("hello!\n")
# close automatic, even if exception
坏的做法:
裸 except 会捕获所有异常,包括用户中断信号(如 Ctrl+C),导致程序无法退出。
def bare_except():
while True:
try:
s = input("Input a number: ")
x = int(s)
break
except: # oops! can't CTRL-C to exit
print("Not a number, try again")
好的做法:
明确指定要捕获的异常类型,例如 ValueError。
def bare_except():
while True:
try:
s = input("Input a number: ")
x = int(s)
break
except ValueError: # 比这更好的是用 ValueError
print("Not a number, try again")
坏的做法: 默认参数是可变对象(如列表)时,会在多次调用间共享状态,导致非预期结果。
def mutable_default_arguments():
def append(n, l=[]):
l.append(n)
return l
l1 = append(0) # [0]
l2 = append(1) # [0, 1]
好的做法:
将默认参数设为 None,在函数内部初始化可变对象。
def mutable_default_arguments():
def append(n, l=None):
if l is None:
l = []
l.append(n)
return l
l1 = append(0) # [0]
l2 = append(1) # [1]
坏的做法: 使用循环构建字典或列表,代码冗长。
squares = {}
for i in range(10):
squares[i] = i * i
好的做法: 使用字典推导式,简洁高效。
odd_squares = {i: i * i for i in range(10)}
坏的做法: 过度嵌套的推导式会牺牲可读性,难以维护。
c = [
sum(a[n * i + k] * b[n * k + j] for k in range(n))
for i in range(n)
for j in range(n)
]
好的做法: 对于复杂逻辑,使用普通循环结构。
c = []
for i in range(n):
for j in range(n):
ij_entry = sum(a[n * i + k] * b[n * k + j] for k in range(n))
c.append(ij_entry)
坏的做法:
使用 type() == 进行类型检查不支持继承关系,且不够 Pythonic。
def checking_type_equality():
Point = namedtuple('Point', ['x', 'y'])
p = Point(1, 2)
if type(p) == tuple:
print("it's a tuple")
else:
print("it's not a tuple")
好的做法:
使用 isinstance(),它能正确处理继承和多态。
def checking_type_equality():
Point = namedtuple('Point', ['x', 'y'])
p = Point(1, 2)
# probably meant to check if is instance of tuple
if isinstance(p, tuple):
print("it's a tuple")
else:
print("it's not a tuple")
坏的做法:
使用 == 比较单例对象(如 None, True, False)是不安全的。
def equality_for_singletons(x):
if x == None:
pass
if x == True:
pass
if x == False:
pass
好的做法:
使用 is 运算符进行身份比较。
def equality_for_singletons(x):
# better
if x is None:
pass
if x is True:
pass
if x is False:
pass
坏的做法:
显式调用 bool() 或 len() 检查通常是不必要的。
def checking_bool_or_len(x):
if bool(x):
pass
if len(x) != 0:
pass
好的做法: 直接使用变量作为条件表达式。
def checking_bool_or_len(x):
# usually equivalent to
if x:
pass
坏的做法:
使用 range(len()) 遍历列表索引,不如直接迭代元素直观。
def range_len_pattern():
a = [1, 2, 3]
for i in range(len(a)):
v = a[i]
...
b = [4, 5, 6]
for i in range(len(b)):
av = a[i]
bv = b[i]
...
好的做法:
直接迭代元素,或使用 enumerate 获取索引,使用 zip 并行迭代。
def range_len_pattern():
a = [1, 2, 3]
# instead
for v in a:
...
# or if you wanted the index
for i, v in enumerate(a):
...
# instead use zip
for av, bv in zip(a, b):
...
坏的做法: 通过键访问值需要两次查找,效率较低。
def not_using_dict_items():
d = {"a": 1, "b": 2, "c": 3}
for key in d:
val = d[key]
...
好的做法:
使用 .items() 直接解包键值对。
def not_using_dict_items():
d = {"a": 1, "b": 2, "c": 3}
for key, val in d.items():
...
坏的做法: 通过索引访问元组元素增加了不必要的复杂度。
mytuple = 1, 2
x = mytuple[0]
y = mytuple[1]
好的做法: 使用序列解包。
mytuple = 1, 2
x, y = mytuple
坏的做法:
time.time() 精度较低,且受系统时钟调整影响。
def timing_with_time():
start = time.time()
time.sleep(1)
end = time.time()
print(end - start)
好的做法:
使用 time.perf_counter(),提供更高精度的单调时钟。
def timing_with_time():
# more accurate
start = time.perf_counter()
time.sleep(1)
end = time.perf_counter()
print(end - start)
坏的做法:
在生产环境中使用 print 无法控制日志级别和输出格式。
def print_vs_logging():
print("debug info")
print("just some info")
print("bad error")
好的做法:
使用标准库 logging 模块,支持分级管理和格式化。
def print_vs_logging():
# versus
# in main
level = logging.DEBUG
fmt = '[%(levelname)s] %(asctime)s - %(message)s'
logging.basicConfig(level=level, format=fmt)
# wherever
logging.debug("debug info")
logging.info("just some info")
logging.error("uh oh :(")
坏的做法:
shell=True 会将命令字符串传递给 Shell 解释器,存在注入风险且产生额外进程开销。
subprocess.run(["ls -l"], capture_output=True, shell=True)
好的做法: 拒绝从 shell 执行,直接传递参数列表。
subprocess.run(["ls", "-l"], capture_output=True)
坏的做法: 使用原生列表进行数值计算效率低下。
def not_using_numpy_pandas():
x = list(range(100))
y = list(range(100))
s = [a + b for a, b in zip(x, y)]
好的做法: 利用 NumPy 的向量化操作,性能更快。
import numpy as np
def not_using_numpy_pandas():
# 性能更快
x = np.arange(100)
y = np.arange(100)
s = x + y
坏的做法: 通配符导入污染命名空间,导致变量来源不明。
from itertools import *
count()
好的做法: 显式导入需要的具体函数或模块。
from mypackage.nearby_module import awesome_function
def main():
awesome_function()
if __name__ == '__main__':
main()
为了帮助开发者避免上述坏习惯,建议引入以下工具链:
将这些工具集成到 CI/CD 流程中,可以在代码合并前自动拦截不符合规范的提交。
养成良好的 Python 编程习惯是一个持续的过程。除了掌握上述 18 个要点外,还应深入理解 Python 的设计哲学(The Zen of Python),阅读优秀的开源项目源码,并积极参与社区讨论。通过不断重构和优化代码,不仅能提升个人技术能力,也能为团队贡献更高质量的软件产品。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online