很多初学者觉得Python语法简洁、上手快,但实际开发中,一些隐藏的细节常常让人猝不及防。下面总结了10个最容易踩的语法坑,基本每个新手都至少遇到过一半。
1. 可变默认参数
定义函数时,默认参数只在定义阶段求值一次。如果使用可变对象(如列表),多次调用会共享同一个对象,导致意外数据累积。- def add(item, lst=[]):
- lst.append(item)
- return lst
- print(add(1)) # [1]
- print(add(2)) # [1, 2] ← 结果不符合预期
复制代码 正确做法是将默认参数设为None,函数内部再创建新列表:- def add(item, lst=None):
- if lst is None:
- lst = []
- lst.append(item)
- return lst
复制代码
2. 闭包延迟绑定
在循环中创建lambda函数时,lambda内部引用的变量i是延迟绑定的,循环结束后i取最终值,导致所有函数返回相同结果。- funcs = [lambda: i for i in range(5)]
- print([f() for f in funcs]) # [4, 4, 4, 4, 4]
复制代码 修复方法:使用默认参数捕获当前值,即lambda i=i: i。
3. is vs ==
is比较的是对象的内存地址,==比较的是值。Python为了性能,对小整数(-5到256)进行了缓存,因此a is b可能为True,但超出范围则为False。- a = 256; b = 256
- print(a is b) # True (小整数池)
- a = 257; b = 257
- print(a is b) # False
复制代码 判断相等一律使用==,is只应用于与None比较(如if x is None)。
4. 缩进导致的逻辑错误
在Python中,缩进是语法的一部分。错误的缩进会导致代码逻辑完全改变,甚至引发IndentationError。- def foo(x):
- if x > 0:
- print("正数")
- return "完成" # 这个return不在if内,无条件执行
复制代码 务必统一使用4个空格,避免混用Tab和空格。
5. 修改正在遍历的列表
遍历列表时直接删除元素会导致索引错乱,漏删或越界。- nums = [2, 4, 6, 8]
- for n in nums:
- if n % 2 == 0:
- nums.remove(n)
- print(nums) # 结果可能不如预期
复制代码 正确做法是创建副本遍历,或使用列表推导式:- nums = [n for n in nums if n % 2 != 0]
- # 或
- for n in nums[:]:
- if n % 2 == 0:
- nums.remove(n)
复制代码
6. 字符串不可变,但变量名可以“变”
字符串是不可变对象,拼接操作实际上是创建新对象。频繁拼接会带来性能开销。- s = "hello"
- s = s + " world" # 生成新对象,原字符串不变
复制代码 大量字符串拼接时,推荐使用''.join(list)或f-string。
7. 浅拷贝 vs 深拷贝
列表的.copy()、list()、切片[:]都是浅拷贝,只复制外层引用,嵌套的可变对象仍共享。- import copy
- a = [[1, 2], [3, 4]]
- b = a.copy()
- b[0][0] = 999
- print(a) # [[999, 2], [3, 4]] 原列表被修改
复制代码 若要完全独立,必须使用copy.deepcopy()。
8. except捕获范围太大
裸except会捕获包括KeyboardInterrupt(Ctrl+C)在内的所有异常,导致程序无法正常退出。- try:
- result = 10 / 0
- except: # 不推荐
- print("出错了")
复制代码 始终指定具体的异常类型,如except ZeroDivisionError。
9. 字典键必须可哈希
字典的键必须是不可变且可哈希的类型。列表、字典、集合都不能用作键,因为它们可变。- d = {[1, 2]: "value"} # TypeError: unhashable type: 'list'
复制代码 元组是可哈希的,可以作为键(前提是其元素也可哈希)。
10. for...else 和 while...else
else子句在循环没有被break打断时才会执行。这个设计常用于搜索场景,但容易让人误解。- for i in range(5):
- if i == 10:
- break
- else:
- print("循环正常结束") # 会执行,因为从未break
复制代码 记住:else属于循环语句,不是if。
以上10个陷阱都是Python语言设计中的细节,理解它们能帮你写出更稳健的代码。踩过这些坑,才算是真正入了Python的门。 |