每一种语言都有各自的场景, 各自的设计, 有各自的坑.
近几年来使用场景越来越广泛的 Python, 都有哪些常见的坑(陷阱). 下面是我从初学以来, 碰到过的陷阱(跟其他语言不一样的地方).
1.函数内的变量作用域
函数内部, 代码块之内定义的变量,代码块之外可以访问。
i = 10
if i>5:
square = i*i
print(square)
在 Java/C#/C++/C 之类的语言中, 类似上面的代码逻辑会跑错, 因为 代码运行到最后一行的时候, square 出来作用域.
但在 python 里面, 这个代码是能够正常工作的. 在 if/for 代码块之外, 变量可以被访问到.
2.函数内部有做赋值的变量, 必须在函数内定义
a = 1
def works():
print(a)
def fail():
print(a)
a = 2
python读入并编译这段代码时,发现def里面有一个对a赋值的语句,然后决定a在函数中属于本地变量名。
那么当print(a)
执行时,就会对print(a)
按照LEGB原则执行搜索,发现a属于本地变量名但还未赋值。出现变量未赋值前使用的问题。
解决方案:
def work_with_global_var():
global a
print(a)
a = 2
3.嵌套在循环中的lambda
func_list = []
for i in range(5):
func_list.append(lambda x: x + i)
这样生成了一个元素为函数的列表,其中每个函数都可以传入一个参数,然后返回的是传入值和0,1,2,3,4的和。
当然理想情况下是这样,现实并不会如此。
func_list[0](1)
5
func_list[1](1)
5
func_list[2](1)
5
3次调用,每次调用的结果都是4+1的和。
发生这种现象的原因在于变量i在函数调用时才进行查找,而此时循环已经结束,i=4,所以所有函数对象在被调用时参数i全部都为5.
避免出问题的方法:不使用闭包, 或者在 循环体内调用闭包函数(避免 lambda 延迟调用)
funcs = []
results = []
for x in range(7):
def some_func():
return x
funcs.append(some_func)
results.append(some_func()) # 注意这里函数被执行了
funcs_results = [func() for func in funcs]
print(results)
print(funcs_results)
# 输出:[0, 1, 2, 3, 4, 5, 6]
# 输出:[6, 6, 6, 6, 6, 6, 6]
即使每次在迭代中some_func中的x值都不相同,所有的函数还是都返回6.
4.集合的 append(), update() 等操作 不返回对象(原地修改变量)
some_list = [1, 2, 3]
some_dict = {
"key_1": 1,
"key_2": 2,
"key_3": 3
}
some_list = some_list.append(4)
some_dict = some_dict.update({"key_4": 4})
print(some_list)
print(some_dict)
# 输出:None
# 输出:None
这是一个写法错误,并不是特殊用法。因为列表和字典的操作函数,比如list.append、list.extend、dict.update等都是原地修改变量,不创建也不返还新的变量
5.可变对象作函数默认参数
对于可变对象参数而言,当函数被调用并对该参数进行原地修改,默认参数将发生变化并进而影响接下来的函数调用。
先用可变对象作为默认参数编写一个函数。
def test(a=[]):
a.append(1)
print(a)
接下来多次调用test函数,你一定以为每次打出来的都是一个包含1的列表。但是,事实并不是这样。
test()
[1]
test()
[1, 1]
test()
[1, 1, 1]
标准解决方案:
def test(a=None):
if a is None:
a = []
a.append(1)
print(a)
6.finally 中有返回时, 以 finally的返回为准
def some_func():
try:
return 'from_try'
finally:
return 'from_finally'
some_func()
# 始终输出:from_finally
函数的返回值由最后执行的 return 语句决定. 由于 finally 子句一定会执行, 所以 finally 子句中的 return 将始终是最后执行的语句