Python函数的默认参数的设计【原创】
在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)
4523801232
>>> p = p + 1
>>> id(p)
4523801264
>>> p = 11
>>> id(p)
4523801552 >>> p = []
>>> id(p)
4527080640
>>> p.append(11)
>>> id(p)
4527080640
根本原因
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.
由此可知:
- 在运行代码时,运行到函数定义时,默认参数的表达式就被执行了。
- 函数调用时,不会再次运行默认参数的表达式。⚠️ 这点和Ruby完全不同。
- 由此可知,如果默认参数,指向一个不变对象,例如L = 1。那么在函数调用时,在函数体内对L重新赋值,L其实是一个新的指针, 指向的是一个新的内存地址。而原来默认参数L本身及指向的内存地址,已经储存在最开始编译时的函数定义中。可以用__default__查看。
- 如果默认参数指向的是一个可变对象,如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
4353170064
1
self 4353170064
4353171088
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
4337586048
[1]
self 4337586048
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函数中默认参数的一些坑
一.默认参数 python为了简化函数的调用,提供了默认参数机制: 这样在调用pow函数时,就可以省略最后一个参数不写: 在定义有默认参数的函数时,需要注意以下: 必选参数必须在前面,默认参数在后: ...
- 经典面试题-python函数之默认参数
1.可变的默认参数----list 示例: def add(a, mylist=[]): # print(id(mylist)) mylist.append(a) return mylist pri ...
- python定义函数时默认参数注意事项
如果在调用一个函数时,没有传递默认参数,则函数内的默认参数是对函数的默认参数属性__defaults__的引用, 如 def func(arg1=[]): arg1.append(2) 调用func时 ...
- Python中的默认参数(转)
add by zhj: Python设计者为何将默认参数设计成这样呢?参见Python函数参数默认值的陷阱和原理深究 原文:https://github.com/acmerfight/insight_ ...
- Python 函数中,参数是传值,还是传引用?
在 C/C++ 中,传值和传引用是函数参数传递的两种方式,在Python中参数是如何传递的?回答这个问题前,不如先来看两段代码. 代码段1: def foo(arg): arg = 2 print(a ...
- [python]一个关于默认参数的老问题和一个有关优化的新问题
一个老问题: def func(defau=[]): defau.append(1) return defau print(func())#print[1] print(func())#print[1 ...
- 对C++虚函数使用默认参数的注意事项
本文为大便一箩筐的原创内容,转载请注明出处,谢谢:http://www.cnblogs.com/dbylk/ 备忘一个关于虚函数的小知识点 使用多态调用一个类型中定义的虚函数时,编译器会根据指针的当前 ...
- Python之定义默认参数
定义函数的时候,还可以有默认参数. 例如Python自带的 int() 函数,其实就有两个参数,我们既可以传一个参数,又可以传两个参数: >>> int('123') 123 > ...
- C++中函数的默认参数
使用方法: (1)在函数声明或定义时,直接对参数赋值,该参数就是默认参数. (2)在函数调用时,省略部分或全部参数,这时就会使用默认参数进行代替. 注意事项: (1)一般在声明函数是设置默认参数. 如 ...
随机推荐
- 不离开Emacs完成简单程序的编辑编译运行(windows或Linux)
(1)打开Emacs (2)建立一个新的程序文件. 执行 C-x C-f, 然后在屏幕的底部出现minibuffer,光标提示你输入文件名称, 文件名称要带上后缀名,如hello.cpp.回车,然后开 ...
- Linux下安装db2V9.7
vi /etc/hosts(127.0.0.1 localhost192.168.1.53 linux-wmv8) vi /etc/services db2inst1 50000/tcp(加在最后) ...
- 关于typedef和struct
在struct中使用自身,需要加struct关键字,无论带不带typedef,例如: struct A { int a; struct A *pA; }; 在定义struct方面尽量不要使用typed ...
- ansible模块之yum、pip、service、corn、user、group
ansible相关模块 yum rpm 和yum 的区别 rpm:全称redhat package manager (红帽包管理器) 不能解决包之间的依赖关系 yum:可以解决依赖关系 yum 源配置 ...
- ArcGIS Engine空间分析之缓冲区分析的实现
缓冲分析(BufferAnalysis)的结果是一个面状要素——即缓冲要素,点状要素.线状要素和面状要素,被缓冲分析功能处理过之后,它们的周围产生一个缓冲区域,该区域即新产生的面状要素. 在缓冲方向上 ...
- 使用XPath
XPath----XML路径语言 XPath概览 XPath是一门在XML文档中查找信息的语言,它提供了非常简洁明了的路径选择表达式. XPath常用规则 表达式 描 述 nodename 选取此节 ...
- TICK技术栈(三)InfluxDB安装及使用
1.什么是InfluxDB? InfluxDB是一个用Go语言开发的时序数据库,用于处理高写入和查询负载,专门为带时间戳的数据编写,对DevOps监控,IoT监控和实时分析等应用场景非常有用.通过自定 ...
- 说说 Python3 中的数字处理
最近在处理订单相关的问题,踩了数字的一些坑,在此记录下. 其中有问题的代码涉及金额比较,便于描述,假设了下面一段代码 def is_paid(pay_price, paid_price): retur ...
- csps模拟测试70
又炸了,T1没开$long long$,炸掉$50pts$,昨天因为SB错误挂掉$100pts$. 我kuku了,以后细心点吧.
- 『题解』洛谷P2170 选学霸
更好的阅读体验 Portal Portal1: Luogu Description 老师想从\(N\)名学生中选\(M\)人当学霸,但有\(K\)对人实力相当,如果实力相当的人中,一部分被选上,另一部 ...