Python函数的默认参数设计示例详解


Posted in Python onDecember 01, 2019

在Python教程里,针对默认参数,给了一个“重要警告”的例子:

def f(a, L=[]):
  L.append(a)
  return L

print(f(1))
print(f(2))
print(f(3))

默认值只会执行一次,也没说原因。会打印出结果:

[1]
[1, 2]
[1, 2, 3]

因为学的第一门语言是Ruby,所以感觉有些奇怪。 但肯定的是方法f一定储存了变量L。

准备知识:指针

p指向不可变对象,比如数字。则相当于p指针指向了不同的内存地址。

p指向的是可变对象,比如list。list自身的改变,并不会改变list对象自身所在的内存地址。所以p指向的内存地址不变。

>>> p = 1
>>> id(p)
>>> p = p + 1
>>> id(p)
>>> p = 11
>>> id(p)

>>> p = []
>>> id(p)
>>> p.append(11)
>>> id(p)

根本原因

Python函数的参数默认值,是在编译阶段就绑定了。(写代码时就定义了。)

下面是一段从Python Common Gotchas中摘录的原因解释:

Python's default arguments are evaluated once when the function is defined, not each time the function is called (like it is in say, Ruby). This means that if you use a mutable default argument and mutate it, you will and have mutated that object for all future calls to the function as well.

由此可知:

  1. 在运行代码时,运行到函数定义时,默认参数的表达式就被执行了。
  2. 函数调用时,不会再次运行默认参数的表达式。⚠️ 这点和Ruby完全不同。
  3. 由此可知,如果默认参数,指向一个不变对象,例如L = 1。那么在函数调用时,在函数体内对L重新赋值,L其实是一个新的指针, 指向的是一个新的内存地址。而原来默认参数L本身及指向的内存地址,已经储存在最开始编译时的函数定义中。可以用__default__查看。
  4. 如果默认参数指向的是一个可变对象,如list, 那么L.append(a)是对可变对象自身的修改,L指向的内存地址不变。所以每次调用函数,默认参数取出的都是这个内存地址的对象。

第三条,修改上面的例子:

def f(a, L = 1):
  L = a
  print(id(L))
  return L

print("self",id(f.__defaults__[0]))
print(f(1))
print("self",id(f.__defaults__[0]))
print(f(33))
print("self",id(f.__defaults__[0]))

#运行结果:
self 4353170064
1
self 4353170064
33
self 4353170064

默认参数L,在编译阶段就绑定了,储存在__default__内。函数体内的L = a表达式,生成的是新的变量。返回的L是新的变量,和默认参数无关。

第四条,还是上面的例子, 改一下默认参数的类型为可变对象list:

def f(a, L = []):
  L.append(a)
  print(id(L))
  return L
# L = f(1)
print("self",id(f.__defaults__[0]))
print(f(1))
print("self",id(f.__defaults__[0]))
print(f(33))
print("self",id(f.__defaults__[0]))
#返回结果
self 4337586048
[1]
self 4337586048
[1, 33]
self 4337586048

由id号可知,返回的是默认参数自身。

如何避免这个陷阱带来不必要麻烦

def f(a, L = None):
  if L is None:
    L = []
  L.append(a)
  return L

为什么Python要这么设计

 StackOverflow 上争论很多。

Ruby之所以没有这个问题,我想是因为Ruby的def关键字定义了一个封闭作用域,任何数据都必须通过参数传入到方法内,才能用。

和Ruby比,Python参数的作用被大大消弱了。Python的出现晚于Ruby,其创始人肯定参考了Ruby的设计。抛弃了这个设计,选择了类似javascript的函数方式。def定义的函数,执行时是可以接收外部作用域的变量的。

有观点认为:

出于Python编译器的实现方式考虑,函数是一个内部一级对象。而参数默认值是这个对象的属性。在其他任何语言中,对象属性都是在对象创建时做绑定的。因此,函数参数默认值在编译时绑定也就不足为奇了。

本文参考了:http://cenalulu.github.io/python/default-mutable-arguments/#toc1 ,并加入了自己的理解。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。

Python 相关文章推荐
Python实现根据IP地址和子网掩码算出网段的方法
Jul 30 Python
Linux中安装Python的交互式解释器IPython的教程
Jun 13 Python
python实现list元素按关键字相加减的方法示例
Jun 09 Python
opencv改变imshow窗口大小,窗口位置的方法
Apr 02 Python
python3文件复制、延迟文件复制任务的实现方法
Sep 02 Python
Python 自动登录淘宝并保存登录信息的方法
Sep 04 Python
Python全栈之列表数据类型详解
Oct 01 Python
在Python中通过threshold创建mask方式
Feb 19 Python
python语言的优势是什么
Jun 17 Python
python实现文件+参数发送request的实例代码
Jan 05 Python
DRF使用simple JWT身份验证的实现
Jan 14 Python
如何将numpy二维数组中的np.nan值替换为指定的值
May 14 Python
python线程定时器Timer实现原理解析
Nov 30 #Python
python线程信号量semaphore使用解析
Nov 30 #Python
Python一行代码解决矩阵旋转的问题
Nov 30 #Python
Numpy之将矩阵拉成向量的实例
Nov 30 #Python
numpy np.newaxis 的实用分享
Nov 30 #Python
Python如何使用函数做字典的值
Nov 30 #Python
关于Numpy中的行向量和列向量详解
Nov 30 #Python
You might like
php selectradio和checkbox默认选择的实现方法详解
2013/06/29 PHP
深入分析PHP引用(&)
2014/09/04 PHP
php array_values 返回数组的值实例详解
2016/11/17 PHP
用JavaScript实现仿Windows关机效果
2007/03/10 Javascript
Jquery使用Firefox FireBug插件调试Ajax步骤讲解
2013/12/02 Javascript
jquery检测input checked 控件是否被选中的方法
2014/03/26 Javascript
jQuery中$.ajax()和$.getJson()同步处理详解
2015/08/12 Javascript
一个极为简单的requirejs实现方法
2016/10/20 Javascript
Canvas 制作动态进度加载水球详解及实例代码
2016/12/09 Javascript
jQuery设置图片等比例缩小的方法
2017/04/29 jQuery
vue.js声明式渲染和条件与循环基础知识
2017/07/31 Javascript
微信小程序input框中加入小图标的实现方法
2018/06/19 Javascript
vue父组件异步获取数据传给子组件的方法
2018/07/26 Javascript
Vue 路由切换时页面内容没有重新加载的解决方法
2018/09/01 Javascript
Node.js 获取微信JS-SDK CONFIG的方法示例
2019/05/21 Javascript
微信公众号H5之微信分享常见错误和问题(小结)
2019/11/14 Javascript
react结合bootstrap实现评论功能
2020/05/30 Javascript
解决idea开发遇到javascript动态添加html元素时中文乱码的问题
2020/09/29 Javascript
Django视图之ORM数据库查询操作API的实例
2017/10/27 Python
python数据分析数据标准化及离散化详解
2018/02/26 Python
Python实现拷贝/删除文件夹的方法详解
2018/08/29 Python
Linux CentOS Python开发环境搭建教程
2018/11/28 Python
python模糊图片过滤的方法
2018/12/14 Python
Python列表删除元素del、pop()和remove()的区别小结
2019/09/11 Python
Python遍历字典方式就实例详解
2019/12/28 Python
Python注释、分支结构、循环结构、伪“选择结构”用法实例分析
2020/01/09 Python
python列表切片和嵌套列表取值操作详解
2020/02/27 Python
css3中的calc函数浅析
2018/07/10 HTML / CSS
详解css3 flex弹性盒自动铺满写法
2020/09/17 HTML / CSS
html5 浏览器支持 如何让所有的浏览器都支持HTML5标签样式
2012/12/07 HTML / CSS
体验完美剃须:The Art of Shaving
2018/08/06 全球购物
面试后的感谢信范文
2014/02/01 职场文书
个人自我剖析材料
2014/02/07 职场文书
综治维稳工作承诺书
2014/08/30 职场文书
2015年小学数学教师个人工作总结
2015/05/25 职场文书
入党积极分子党小组意见
2015/06/02 职场文书