Python 常见陷阱
可变默认参数
看起来,最让 Python 程序员感到惊奇的是 Python 对函数定义中可变默认参数的处理。
def append_to(element, to=[]):
to.append(element)
return to
my_list = append_to(12)
print(my_list)
my_other_list = append_to(42)
print(my_other_list)
每次调用函数时,如果不提供第二个参数,就会创建一个新的列表,所以结果应是这样的:
[12]
[42]
而事实是
[12]
[12, 42]
当函数被定义时,一个新的列表就被创建一次,而且同一个列表在每次成功的调用中都被使用。
当函数被定义时,Python 的默认参数就被创建一次,而不是每次调用函数的时候创建。这意味着,如果您使用一个可变默认参数并改变了它,您将会在未来所有对此函数的调用中改变这个对象。
您应该做的
在每次函数调用中,通过使用指示没有提供参数的默认参数 None
通常是个好选择,来创建一个新的对象。
def append_to(element, to=None):
if to is None:
to = []
to.append(element)
return to
别忘了,您在把列表对象作为第二个参数传入。
有时您可以专门“利用”(或者说特地使用)这种行为来维护函数调用间的状态。这通常用于编写缓存函数。
延迟绑定闭包
另一个常见的困惑是 Python 在闭包(或在周围全局作用域(surrounding global scope))中
绑定变量的方式。
def create_multipliers():
return [lambda x : i x for i in range(5)]
for multiplier in create_multipliers():
print(multiplier(2))
一个包含五个函数的列表,每个函数有它们自己的封闭变量 i
乘以它们的参数,得到::
0
2
4
6
8
而事实是
8
8
8
8
8
五个函数被创建了,它们全都用4乘以 x
。
Python 的闭包是迟绑定。这意味着闭包中用到的变量的值,是在内部函数被调用时查询得到的。
这里,不论任何返回的函数是如何被调用的, i
的值是调用时在周围作用域中查询到的。接着,循环完成, i
的值最终变成了4。
关于这个陷阱有一个普遍严重的误解,它被认为是和Python的 lambdas <python:lambda>
有关。 由 lambda
表达式创建的函数并没什么特别,而且事实上,同样的问题也出现在使用普通的定义上:
def create_multipliers():
multipliers = []
for i in range(5):
def multiplier(x):
return i x
multipliers.append(multiplier)
return multipliers
最一般的解决方案可以说是有点取巧(hack)。由于 Python 拥有在前文提到的为函数默认参数赋值的行为),您可以创建一个立即绑定参数的闭包,像下面这样:
def create_multipliers():
return [lambda x, i=i : i x for i in range(5)]
或者,您可以使用 functools.partial
函数:
from functools import partial
from operator import mul
def create_multipliers():
return [partial(mul, i) for i in range(5)]
https://pythonguidecn.readthedocs.io/zh/latest/writing/gotchas.html
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 bin07280@qq.com
文章标题:Python 常见陷阱
文章字数:754
本文作者:Bin
发布时间:2018-12-31, 13:59:52
最后更新:2019-08-06, 00:07:35
原始链接:http://coolview.github.io/2018/12/31/Python/Python%20%E5%B8%B8%E8%A7%81%E9%99%B7%E9%98%B1/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。