Python陷阱:为什么不能用可变对象作为默认参数的值
2018-01-26 14:02
302 查看
上次分享过一篇关于图解Python变量与赋值的文章,今天接着这个话题继续聊一聊关于赋值的一些坑。先来看一道题目:
我们似乎发现了一个Bug,每次用相同的方式调用函数
从上面可以看出,函数的返回值其实是同一个列表对象,因为他们的id值是一样的,只不过是列表中的元素在变化。为什么会这样呢?
这要从函数的特性说起,在 Python 中,函数是第一类对象(function is the first class object),换而言之,函数也是对象,跟整数、字符串一样可以赋值给变量、当做参数传递、还可以作为返回值。函数也有自己的属性,比如函数的名字、函数的默认参数列表。
func)会指向该函数对象,记住,至始至终,不管该函数调用多少次,函数对象只有一个,就是function object,不会因为调用多次而出现多个函数对象。
![](https://foofish.net/images/function_default_args1.jpg)
函数对象生成之后,它的属性:名字和默认参数列表都将初始化完成。
![](https://foofish.net/images/function_default_args2.jpg)
初始化完成时,属性
当函数第一次被调用时,就是第一次执行
![](https://foofish.net/images/function_default_args3.jpg)
第二次调用
![](https://foofish.net/images/function_default_args4.jpg)
第三次、四次依此类推。
所以现在你应该明白为什么调用同一个函数,返回值确每次都不一样了吧。因为他们共享的是同一个列表(numbers)对象,只是每调用一次就往该列表中增加了一个元素
如果我们显示地指定 numbers 参数,结果截然不同。
![](https://foofish.net/images/function_default_args5.jpg)
因为numbers被重新赋值了,它不再指向原来初始化时的那个列表了,而是指向了我们传递过去的那个新列表对象,因此返回值变成了 [10, 11, 1]
那么我们应该如何避免前面那种情况发生呢?就是不要用可变对象作为参数的默认值。
正确方式:
如果调用时没有指定参数,那么调用方法时,默认参数 numbers 每次都被重新赋值了,所以,每次调用的时候numbers都将指向一个新的对象。这就是与前者的区别所在。
那么,是不是说我们永远都不应该用可变对象来作为参数的默认值了吗?并不是,既然Python有这样的语法,就一定有他的应用场景,就像 for ... else 语法一样。我们可以用可变对象来做缓存功能。
例如:计算一个数的阶乘时可以用一个可变对象的字典当作缓存值来实现缓存,缓存中保存计算好的值,第二次调用的时候就无需重复计算,直接从缓存中拿。
输出:
第二次调用的时候,直接从 cache 中拿了值,所以,你说用可变对象作为默认值是 Python 的缺陷吗?也并不是,对吧!你还是当作一种特性来使用。
参考文档:https://docs.python.org/3/reference/compound_stmts.html#function-definitions
>>> def func(numbers=[], num=1): ... numbers.append(num) ... return numbers >>> func() [1] >>> func() [1, 1] >>> func() [1, 1, 1]
我们似乎发现了一个Bug,每次用相同的方式调用函数
func()时,返回结果竟然不一样,而且每次返回的列表在不断地变长。
>>> id(func()) 4330472840 >>> id(func()) 4330472840
从上面可以看出,函数的返回值其实是同一个列表对象,因为他们的id值是一样的,只不过是列表中的元素在变化。为什么会这样呢?
这要从函数的特性说起,在 Python 中,函数是第一类对象(function is the first class object),换而言之,函数也是对象,跟整数、字符串一样可以赋值给变量、当做参数传递、还可以作为返回值。函数也有自己的属性,比如函数的名字、函数的默认参数列表。
# 函数的名字 >>> func.__name__ 'func' # 函数的默认参数列表 >>> func.__defaults__ ([1, 1, 1, 1, 1], 1)
def是一条可执行语句,Python 解释器执行 def 语句时,就会在内存中就创建了一个函数对象(此时,函数里面的代码逻辑并不会执行,因为还没调用嘛),在全局命名空间,有一个函数名(变量叫
func)会指向该函数对象,记住,至始至终,不管该函数调用多少次,函数对象只有一个,就是function object,不会因为调用多次而出现多个函数对象。
![](https://foofish.net/images/function_default_args1.jpg)
函数对象生成之后,它的属性:名字和默认参数列表都将初始化完成。
![](https://foofish.net/images/function_default_args2.jpg)
初始化完成时,属性
__default__中的第一个默认参数 numbers 指向一个空列表。
当函数第一次被调用时,就是第一次执行
func()时,开始执行函数里面的逻辑代码(此时函数不再需要初始化了),代码逻辑就是往numbers中添加一个值为1的元素
![](https://foofish.net/images/function_default_args3.jpg)
第二次调用
func(),继续往numbers中添加一个元素
![](https://foofish.net/images/function_default_args4.jpg)
第三次、四次依此类推。
所以现在你应该明白为什么调用同一个函数,返回值确每次都不一样了吧。因为他们共享的是同一个列表(numbers)对象,只是每调用一次就往该列表中增加了一个元素
如果我们显示地指定 numbers 参数,结果截然不同。
>>> func(numbers=[10, 11]) [10, 11, 1]
![](https://foofish.net/images/function_default_args5.jpg)
因为numbers被重新赋值了,它不再指向原来初始化时的那个列表了,而是指向了我们传递过去的那个新列表对象,因此返回值变成了 [10, 11, 1]
那么我们应该如何避免前面那种情况发生呢?就是不要用可变对象作为参数的默认值。
正确方式:
>>> def func(numbers=None, num=1): ... if numbers is None: ... numbers = [num] ... else: ... numbers.append(num) ... return numbers ... >>> func() [1] >>> func() [1] >>> func() [1]
如果调用时没有指定参数,那么调用方法时,默认参数 numbers 每次都被重新赋值了,所以,每次调用的时候numbers都将指向一个新的对象。这就是与前者的区别所在。
那么,是不是说我们永远都不应该用可变对象来作为参数的默认值了吗?并不是,既然Python有这样的语法,就一定有他的应用场景,就像 for ... else 语法一样。我们可以用可变对象来做缓存功能。
例如:计算一个数的阶乘时可以用一个可变对象的字典当作缓存值来实现缓存,缓存中保存计算好的值,第二次调用的时候就无需重复计算,直接从缓存中拿。
def factorial(num, cache={}): if num == 0: return 1 if num not in cache: print('xxx') cache[num] = factorial(num - 1) * num return cache[num] print(factorial(4)) print("-------") print(factorial(4))
输出:
---第一次调用--- xxx xxx xxx xxx 24 ---第二次调用--- 24
第二次调用的时候,直接从 cache 中拿了值,所以,你说用可变对象作为默认值是 Python 的缺陷吗?也并不是,对吧!你还是当作一种特性来使用。
参考文档:https://docs.python.org/3/reference/compound_stmts.html#function-definitions
相关文章推荐
- Python陷阱:为什么不能用可变对象作为函数的默认参数值
- Python陷阱:为什么不能用可变对象作为函数的默认参数值
- python--可变对象作为默认参数
- 理解python中可变对象作为默认参数
- 理解python中可变对象作为默认参数
- HashMap中的key为什么不能为可变对象(除非重写它的hashcode方法和equals方法)
- Python - 在定义函数时,为什么默认参数不能放在必选参数前面?
- HashMap的陷阱,将可变对象作为key
- Python 函数默认参数不能使用可变对象,如List,dict
- python坑:可变对象作为函数默认值
- //这里为什么定义个display()全局函数,作为舞台,让对象唱戏,不能直接把他们放在主函数里面吗,这样写好处在哪,两者区别在哪?????
- Python - 在定义函数时,为什么默认参数不能放在必选参数前面?
- Python 为什么list不能作为字典的key?
- python的raw_input()函数。 函数的可变对象和不可变对象作为参数传递。
- Python中的可变、不可变对象和赋值技巧
- python3在pycharm中为什么导入random模块不能用? TypeError: 'module' object is not callable
- C++的const类成员函数(解释为什么非const成员函数不能访问const对象的数据成员)
- java类中用类的数组作为函数参数传给类,为什么不能直接对类的数组进行操作?
- 使用IShellFolder::EnumObjects()不能正确枚举虚目录“我的电脑”下的对象,为什么?
- [置顶] 为什么要阅读源代码?如何有效的阅读源代码? 选一些比较优秀的开源产品作为源代码阅读对象?