Python为何不能用可变对象作为默认参数的值
作者:FOOFISH-PYTHON之禅 发布时间:2022-11-30 13:42:04
先来看一道题目:
>>> 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,不会因为调用多次而出现多个函数对象。
函数对象生成之后,它的属性:名字和默认参数列表都将初始化完成。
初始化完成时,属性 __default__ 中的第一个默认参数 numbers 指向一个空列表。
当函数第一次被调用时,就是第一次执行 func()时,开始执行函数里面的逻辑代码(此时函数不再需要初始化了),代码逻辑就是往numbers中添加一个值为1的元素
第二次调用 func(),继续往numbers中添加一个元素
第三次、四次依此类推。
所以现在你应该明白为什么调用同一个函数,返回值确每次都不一样了吧。因为他们共享的是同一个列表(numbers)对象,只是每调用一次就往该列表中增加了一个元素
如果我们显示地指定 numbers 参数,结果截然不同。
>>> func(numbers=[10, 11])
[10, 11, 1]
因为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://foofish.net/python-tricks.html


猜你喜欢
- 这篇文章主要介绍了Python定时器线程池原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可
- 痛点引出在平时的开发当中,渲染侧边栏导航菜单有时会遇到过需要侧边栏有多层甚至无限层级的问题。此时更优雅的方式便是使用递归组件<el-m
- (一)行号显示和排序 1.SQL Server的行号 A.SQL 2000使用identity(int,1,1)和临时表,可以显示行号 SE
- <?php session_start(); $_SESSION['username']="zhuzhao&
- 做为一个专职的页面重构者, 我们从事的工作简单的说就是“将设计稿转换成WEB页面”,这一过程可以很简单到直接把PSD从里导出成网页;也可复杂
- 为什么要同步SQL Server 2000 数据库,它都用在什么场合SQL Server 2000 数据库同步配置的原理从0开始一步一步配置
- 问题:使用PyQt5开发桌面程序,实现功能为:按下按键,打开文件夹,选择文件夹,并将路径显示出来。解决方法:一、主要函数(直接能运行的代码见
- 如何导入自己的模块在实际的编程生活当中,我们除了会去import已经存在的包外,当然还会偶尔自定义一些模块,然后来导入,其实一般而言,自定义
- 为网页设置防火墙的主要目的是根据网页内容对不同来访者提供不同的服务,利用Java Script或VB Script,我们很容易做到这一点。但
- 虽然在win2003配置PHP有点非主流,但你还是要会怎么弄。你也可以将本文的虚拟机看成是服务器,宿主机看成是客户端。不像Linux系统,由
- Go 语言中 encoding/json 包可以很方便的将结构体、数组、字典转换为 json 字符串。引用import "enco
- 当我们的文章表中没有对于文章的评论数字段时,我们该这么写sql语句来显示出评论最多的文章呢?下面本站给大家收集了几种方法,仅供参考:1.se
- 由于 MySQLdb 模块还不支持 Python3.x,所以 Python3.x 如果想连接MySQL需要安装 pymysql 模块。pym
- 本文实例讲述了PHP实现打包下载文件的方法。分享给大家供大家参考,具体如下:/*** 下载文件* @param $img* @return
- 本文实例为大家分享了opencv转换颜色空间更改图片背景的具体代码,供大家参考,具体内容如下思路:1、将BGR转换为HSV颜色空间 2、设置
- 前言Golang语言有诸多优点:静态编译、协程、堪比c语言的高性能。但是也有一些令人发指的地方 —— 经常被人调侃 五行代码,三行错误处理
- 如下所示:import numpy as npnp.set_printoptions(threshold='nan')来源:
- 前言个人一直觉得对学习任何知识而言,概念是相当重要的。掌握了概念和原理,细节可以留给实践去推敲。掌握的关键在于理解,通过具体的实例和实际操作
- 爬虫中scrapy.Request的更多参数scrapy.Request的参数scrapy.Request(url[,callback,me
- 用 xlrd 模块读取 Excelxlrd 安装cmd 中输入pip install xlrd 即可安装 xlrd 模块若失败请自行百度”p