跳到主要内容
Python 中更值得避开的 18 个写法 | 极客日志
Python AI
Python 中更值得避开的 18 个写法 整理了 Python 开发里 18 个常见但不太值得保留的写法,包括字符串拼接、资源管理、裸 except、可变默认参数、过度复杂的推导式、类型与单例判断、计时方式、日志输出、shell=True、原生列表数值计算和 import *。对应的建议基本都很朴素:优先用 f-string、with、明确异常类型、None 作为默认占位、isinstance、is、直接条件判断、enumerate/zip、dict.items、perf_counter、logging、参数列表调用子进程,以及在合适场景下使用 NumPy。最后配合 Black、Flake8/Pylint 和 isort,能把很多低级问题挡在提交前。
极客零度 发布于 2026/6/30 更新于 2026/9/8 28 浏览引言
写 Python,很多时候拼的不是'能不能跑',而是代码是不是容易读、容易改、容易在团队里活下去。新手常见的问题也不神秘:有些写法在小脚本里看不出代价,到了协作、排障、扩展的时候就开始添麻烦了。把这些习惯提前理顺,通常比事后重构省力得多。
下面这 18 条,基本都属于'看着能用,实际上不太值'的类型。不是每一条都影响性能,有些主要伤可维护性;也不是所有场景都绝对不能用,但大多数日常代码里,换一种写法会更稳。
1. 用 + 拼字符串
+ 当然能用,但一旦拼接内容稍微多一点,可读性就开始掉。f-string 更直接,变量位置也一眼能看出来。
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" )
def manual_str_formatting (name, subscribers ):
if subscribers > 100000 :
print (f"Wow {name} ! you have {subscribers} subscribers!" )
else :
print (f"Lol {name} that's not many subs" )
2. 资源释放只靠 finally
finally 能兜底,但把'打开、使用、关闭'这件事全压在手动流程里,代码会越来越散。with 更适合这类资源管理。
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()
def finally_instead_of_context_manager (host, port ):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((host, port))
s.sendall(b'Hello, world' )
3. 文件用完再手动 close() 这类写法在简单脚本里看似没问题,问题是分支一多就容易漏。with open(...) 几乎总是更省心。
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" )
4. 裸 except 裸 except 最大的问题不是'会报错',而是它把你本来不该吞掉的异常也吞了,连中断信号都可能挡住。通常只抓你真正知道怎么处理的异常。
def bare_except ():
while True :
try :
s = input ("Input a number: " )
x = int (s)
break
except :
print ("Not a number, try again" )
def bare_except ():
while True :
try :
s = input ("Input a number: " )
x = int (s)
break
except ValueError:
print ("Not a number, try again" )
5. 默认参数里放可变对象 这是 Python 里最容易踩的老坑之一。默认参数只会在函数定义时求值,所以列表、字典这种可变对象会在多次调用间共享状态。
def mutable_default_arguments ():
def append (n, l=[] ):
l.append(n)
return l
l1 = append(0 )
l2 = append(1 )
def mutable_default_arguments ():
def append (n, l=None ):
if l is None :
l = []
l.append(n)
return l
l1 = append(0 )
l2 = append(1 )
6. 该用推导式的时候还在写循环 这种情况不算错,但会让代码显得比必要的更长。简单映射、过滤、构造数据结构时,推导式通常更合适。
squares = {}
for i in range (10 ):
squares[i] = i * i
odd_squares = {i: i * i for i in range (10 )}
7. 把推导式写得太复杂 推导式不是越'高级'越好。嵌套太深以后,阅读成本会明显上升,调试也不舒服。复杂逻辑我一般会退回普通循环。
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)
8. 用 type(x) == ... 做类型判断 type() 直接比等值,继承关系会被忽略。大多数时候,isinstance() 才是你真正想要的。
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" )
def checking_type_equality ():
Point = namedtuple('Point' , ['x' , 'y' ])
p = Point(1 , 2 )
if isinstance (p, tuple ):
print ("it's a tuple" )
else :
print ("it's not a tuple" )
9. 判断单例对象还在用 == None、True、False 这类对象,身份判断比值比较更明确。尤其是 None,基本都该用 is。
def equality_for_singletons (x ):
if x == None :
pass
if x == True :
pass
if x == False :
pass
def equality_for_singletons (x ):
if x is None :
pass
if x is True :
pass
if x is False :
pass
10. 还在显式写 bool(x) 或 len(x) != 0
很多时候 Python 已经帮你做过这层判断了。直接把对象放进条件里,代码短,也更符合习惯。
def checking_bool_or_len (x ):
if bool (x):
pass
if len (x) != 0 :
pass
def checking_bool_or_len (x ):
if x:
pass
11. 按 range(len(...)) 走列表 这是从 C 风格迁过来的习惯。能直接迭代元素,就别绕一圈拿索引;需要索引时,用 enumerate()。
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]
...
def range_len_pattern ():
a = [1 , 2 , 3 ]
for v in a:
...
for i, v in enumerate (a):
...
for av, bv in zip (a, b):
...
12. 遍历字典时不碰 .items() 只遍历键,再单独取值,没必要。items() 读起来更顺,也少一次查找。
def not_using_dict_items ():
d = {"a" : 1 , "b" : 2 , "c" : 3 }
for key in d:
val = d[key]
...
def not_using_dict_items ():
d = {"a" : 1 , "b" : 2 , "c" : 3 }
for key, val in d.items():
...
13. 取元组元素还在写索引 元组解包本来就是为了让代码别那么啰嗦。两三个值的场景下,这比 mytuple[0]、mytuple[1] 清楚得多。
mytuple = 1 , 2
x = mytuple[0 ]
y = mytuple[1 ]
mytuple = 1 , 2
x, y = mytuple
14. 用 time.time() 量耗时 time.time() 更像'当前时间戳',不是给性能计时准备的。需要测间隔时,perf_counter() 更合适。
def timing_with_time ():
start = time.time()
time.sleep(1 )
end = time.time()
print (end - start)
def timing_with_time ():
start = time.perf_counter()
time.sleep(1 )
end = time.perf_counter()
print (end - start)
15. 记录信息只会用 print print 适合临时调试,不适合长期保留在业务代码里。日志需要级别、格式、输出目标,这些 logging 都能处理。
def print_vs_logging ():
print ("debug info" )
print ("just some info" )
print ("bad error" )
def print_vs_logging ():
level = logging.DEBUG
fmt = '[%(levelname)s] %(asctime)s - %(message)s'
logging.basicConfig(level=level, format =fmt)
logging.debug("debug info" )
logging.info("just some info" )
logging.error("uh oh :(" )
16. 调外部命令时开 shell=True 这个参数用起来省事,但也最容易带来注入问题。命令参数能直接传列表时,就别让 shell 介入。
subprocess.run(["ls -l" ], capture_output=True , shell=True )
subprocess.run(["ls" , "-l" ], capture_output=True )
17. 数值计算一直靠原生列表 小数据量时差别不一定明显,但一旦开始做密集计算,纯 Python 循环通常会显得笨重。NumPy 的向量化操作更适合这种场景。
def not_using_numpy_pandas ():
x = list (range (100 ))
y = list (range (100 ))
s = [a + b for a, b in zip (x, y)]
import numpy as np
def not_using_numpy_pandas ():
x = np.arange(100 )
y = np.arange(100 )
s = x + y
18. 随手 import * 通配符导入最大的问题是命名空间会变脏,代码里到底从哪里来的名字不清楚。排查冲突的时候,这种写法很麻烦。
from itertools import *
count()
from mypackage.nearby_module import awesome_function
def main ():
awesome_function()
if __name__ == '__main__' :
main()
工具链怎么配 这些坏习惯靠人记,效果通常一般。更实际的办法是把格式化、检查和导入排序交给工具:
Black :统一格式,少吵架。
Flake8 / Pylint :提前把明显的问题挑出来,比如未使用的导入、潜在错误、风格偏差。
isort :整理导入顺序,减少杂乱。
把这些放进 CI/CD,能拦住不少'本地看着还行,上线前才发现不对劲'的提交。它们不替你写好代码,但能把最常见的低级失误压下去。
结语 Python 的好习惯不是背条目,而是慢慢形成判断:什么时候该简洁,什么时候该收着写。大部分时候,优先选清楚、稳定、容易维护的写法,比追求炫技更值。真正熟练之后,你会发现很多'高级写法'其实没那么需要,能少绕一点,就已经很好了。
相关免费在线工具 RSA密钥对生成器 生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
Mermaid 预览与可视化编辑 基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
随机西班牙地址生成器 随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online
curl 转代码 解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online