之前已发过的坑请参考Python函数默认值参数的2个坑,Python编程中一定要注意的那些“坑”(一)和Python编程中一定要注意的那些“坑”(二),今天再来填几个坑。
(1)《Python程序设计(第2版)》第124页有一段关于函数默认值参数的代码:
>>> def demo(newitem, old_list=[]):
old_list.append(newitem)
return old_list
>>> demo('5', [1, 2, 3, 4])
[1, 2, 3, 4, '5']
>>> demo('aaa', ['a', 'b'])
['a', 'b', 'aaa']
>>> demo('a')
['a']
>>> demo('b') #意料之外的结果
['a', 'b']
虽然书中紧跟着给出了正确的代码和实现方式:
>>> def demo(newitem, old_list=None):
if old_list is None:
old_list = []
old_list.append(newitem)
return old_list
>>> demo('5', [1, 2, 3, 4])
[1, 2, 3, 4, '5']
>>> demo('aaa', ['a', 'b'])
['a', 'b', 'aaa']
>>> demo('a')
['a']
>>> demo('b')
['b']
但是书中并没有详细说明这里问题的原因是什么。实际上这是一个坑:当定义带有默认值参数的函数时,参数默认值只在函数定义时被解释一次,并被保存到函数的__defaults__成员中,这个__defaults__成员是一个元组,按顺序分别保存着所有默认值参数的当前值,当调用函数而不给默认值参数明确传递参数时,这些默认值参数就使用__defaults__成员中的当前值。因此,如果使用可变序列作为参数默认值并且在函数体内有为其增加元素或修改元素值的行为时,会对后续的调用产生影响。
(2)同样还是这本书上第130页有这样一段关于lambda表达式的代码:
>>> r = []
>>> for x in range(10):
r.append(lambda :x**2)
>>> r[0]() #意料之外的结果
81
>>> r[1]()
81
当然,书上紧跟着这段代码也给出了正确的实现方式:
>>> r = []
>>> for x in range(10):
r.append(lambda n=x: n**2)
>>> r[0]()
0
>>> r[1]()
1
>>> r[3]()
9
和上一个问题一样,书中虽然给出了正确的写法,但是并没有详细解释其中的道理,只是简单地说了一下变量作用域的问题。实际上,如果再深入挖掘一下,试一试下面的代码:
>>> g = lambda :n**2
>>> g()
Traceback (most recent call last):
File "<pyshell#105>", line 1, in <module>
g()
File "<pyshell#104>", line 1, in <lambda>
g = lambda :n**2
NameError: name 'n' is not defined
>>> n = 3
>>> g()
9
>>> n = 5
>>> g()
25
>>> n = 7
>>> g()
49
于是,可以得到这样一个结论,在上面第一段和最后一段代码中,lambda表达式中的x或n实际上是全局变量,它的值取决于调用lambda表达式时这个全局变量的当前值,注意是调用时。而中间一段代码通过参数默认值有效地避免了这样问题。正如本文第一个坑最后提到,函数参数的默认值是在函数定义时确定的。下面的代码或许能够更好地说明这个问题:
>>> n = 3
>>> def f(x=n):
print(x)
>>> f()
3
>>> n = 5
>>> f()
3
>>> n = 7
>>> f() #不影响函数调用结果
3
>>> def f(x=n): #函数参数x依赖于当前n的值
print(x)
>>> f()
7
(3)这个问题是读者看不懂书上关于(1)和(2)的代码,又在我的新书《Python可以这样学》里仍没有找到详细的解释之后到网上找的,但是看完之后发现更糊涂了。
>>> a = []
>>> b = {'num':0, 'sqrt':0}
>>> resource = [1, 2, 3]
>>> for i in resource:
b['num'] = i
b['sqrt'] = i * i
a.append(b)
>>> a #意料之外的结果
[{'num': 3, 'sqrt': 9}, {'num': 3, 'sqrt': 9}, {'num': 3, 'sqrt': 9}]
严格来说,最后这个问题和前面两个问题的性质也不一样,不是Python的坑,而是编程习惯不好造成。在代码中,首先b = {'num':0, 'sqrt':0}这一行是没有必要存在的;其次在循环中,不应该再次使用变量名b了,因为这会导致多次循环中修改同一个字典,这样的话后面的修改会覆盖前面的修改,从而导致错误结果。如果是我写的话,代码应该会写成下面的样子,不需要提前建好字典搭好框架,字典本来就是支持这样直接赋值添加元素的。
>>> a = []
>>> resource = [1, 2, 3]
>>> for i in resource:
b = dict()
b['num'] = i
b['sqrt'] = i * i
a.append(b)
>>> a #正确结果
[{'num': 1, 'sqrt': 1}, {'num': 2, 'sqrt': 4}, {'num': 3, 'sqrt': 9}]