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获取网页状态码示例
Mar 30 Python
Python中pygame的mouse鼠标事件用法实例
Nov 11 Python
Python多线程应用于自动化测试操作示例
Dec 06 Python
实时获取Python的print输出流方法
Jan 07 Python
linux安装python修改默认python版本方法
Mar 31 Python
谈一谈基于python的面向对象编程基础
May 21 Python
python 绘制拟合曲线并加指定点标识的实现
Jul 10 Python
Python scipy的二维图像卷积运算与图像模糊处理操作示例
Sep 06 Python
使用Python制作新型冠状病毒实时疫情图
Jan 28 Python
浅谈python3 构造函数和析构函数
Mar 12 Python
Opencv图像处理:如何判断图片里某个颜色值占的比例
Jun 03 Python
15款Python编辑器的优缺点,别再问我“选什么编辑器”啦
Oct 19 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中的cookie不用刷新就生效的方法
2012/02/04 PHP
php多用户读写文件冲突的解决办法
2013/11/06 PHP
php数组中删除元素之重新索引的方法
2014/09/16 PHP
php生成随机颜色方法汇总
2014/12/03 PHP
浅谈PHP接入(第三方登录)QQ登录 OAuth2.0 过程中遇到的坑
2017/10/13 PHP
PHP dirname简单使用代码实例
2020/11/13 PHP
PPK 谈 JavaScript 的 this 关键字 [翻译]
2009/09/29 Javascript
js 兼容多浏览器的回车和鼠标焦点事件代码(IE6/7/8,firefox,chrome)
2010/04/14 Javascript
关于javascript DOM事件模型的两件事
2010/07/22 Javascript
jQuery hover 延时器实现代码
2011/03/12 Javascript
js批量设置样式的三种方法不推荐使用with
2013/02/25 Javascript
JQuery Tips相关(1)----关于$.Ready()
2014/08/14 Javascript
jquery+easeing实现仿flash的载入动画
2015/03/10 Javascript
EasyUI折叠表格层次显示detailview详解及实例
2016/12/28 Javascript
jquery实现图片放大点击切换
2017/06/06 jQuery
react-router4 配合webpack require.ensure 实现异步加载的示例
2018/01/18 Javascript
js神秘的电报密码 哈弗曼编码实现
2019/09/10 Javascript
解决vue-router 二级导航默认选中某一选项的问题
2019/11/01 Javascript
mpvue微信小程序的接口请求fly全局拦截代码实例
2019/11/13 Javascript
vue.js实现点击图标放大离开时缩小的代码
2021/01/27 Vue.js
python基础while循环及if判断的实例讲解
2017/08/25 Python
Python程序包的构建和发布过程示例详解
2019/06/09 Python
树莓派采用socket方式文件传输(python)
2019/06/22 Python
python flask框架实现重定向功能示例
2019/07/02 Python
django 中QuerySet特性功能详解
2019/07/25 Python
python抓取多种类型的页面方法实例
2019/11/20 Python
Linux操作面试题
2012/05/16 面试题
简单而又朴实的个人求职信分享
2013/12/12 职场文书
小学教师评语大全
2014/04/23 职场文书
小学家长通知书评语
2014/12/31 职场文书
实习指导老师意见
2015/06/04 职场文书
风之谷观后感
2015/06/11 职场文书
病假条格式范文
2015/08/17 职场文书
python 制作一个gui界面的翻译工具
2021/05/14 Python
教你在 Java 中实现 Dijkstra 最短路算法的方法
2022/04/08 Java/Android
muduo TcpServer模块源码分析
2022/04/26 Redis