Re: [问题] None在def中的变化

楼主: bibo9901 (function(){})()   2020-04-06 14:24:06
: 推 pmove: 回b大,你说的官方是出自: 04/02 09:29
: → pmove: https://docs.python.org/3.3/tutorial/controlflow.html 04/02 09:29
: → pmove: Important warning: The default value is evaluated only 04/02 09:30
: → pmove: 下一句就是:This makes a difference when the default is 04/02 09:30
: → pmove: a mutable object such as a list, dictionary, or 04/02 09:31
: → pmove: instances of most classes. 04/02 09:31
: → pmove: 里面就提到mutable object.你要觉得倒果为因,我也没办法 04/02 09:33
: 推 pmove: 我自己是觉得你跟我讲The default value is evaluated only 04/02 09:53
: → pmove: once" 我自己是没办法理解的,但是你告诉我哪些是mutable? 04/02 09:54
: → pmove: 哪些是immutable? mutable/immutable会有哪种情形?这样我 04/02 09:54
: → pmove: 比较好理解。所以才从mutable/immutable切入。 04/02 09:55
: 推 froce: 推楼上,这根本就不是bug,动态语言常这样设计 04/06 07:44
看推文似乎很多人分不清楚值(value)和表达式(expression)
举个例子
1 def connect_db():
2 return ...
3
4 def work(db = connect_db()):
5 db.execute(...)
6
7 DB = connect_db()
8 work( DB )
请问 connect_db() 会执行几次?
正常人类的思考:
嗯... work 函数需要 1 个叫做 db 参数,我也提供一个正确的值,
我不在乎有没有默认值, 反正我没用到,
那么 connect_db() 应该只在第 7 行处执行了一次
但 Python 实际上会执行两次
第一次在第 4 行
第二次在第 7 行
这就是官方文档中
"The default values are evaluated at the point of function definition"
的意思
看到了吗? connect_db 的回传值是 mutable 或 immutable 跟本不重要
究其原因就只是 Python 的规格要求:参数的默认值于函数定义时计算
CPython 也只是照着规格书去实作而已
再举个例子, 这次默认值连型别都没有:
1 def check_missing():
2 raise Exception("You missed one argument")
3
4 def work(db = check_missing()):
5 db.execute(...)
6
7 DB = connect_db()
8 work( DB )
请问以上程式可否顺利执行?
正常人类: 当然可以 (事实上这就是很多动态语言检查“参数是否缺少”的作法)
Python: 不行 (因为 check_missing() 在第 4 行就呼叫了)
Traceback (most recent call last):
File "test.py", line 4, in <module>
def work(db = check_missing()):
File "test.py", line 2, in check_missing
raise Exception("You missed one argument")
Exception: You missed one argument
你看, 我连函数都还没呼叫就挂了!
为什么说这是个雷? 因为这个特性简直莫名奇妙, 完全反直觉, 也没有什么好处.
有人说动态语言常这样设计, 纯属胡说八道
Javascript:
function check_missing(){
throw "Error"
}
function work(db=check_missing()){
...
}
work(db)
顺利执行
C++:
int f(){
throw "error";
}
int work(int data=f()){
return data;
}
int main(){
work(3);
}
顺利执行
Ruby:
def check_missing(number)
raise 'An error has occured'
end
def work(data = check_missing() )
puts(data)
end
work(1)
顺利执行
PHP: 默认值只能使用字面常量(literals)
Lua: 不支援默认值
Java: 不支援默认值
就我知道的语言中只有 Python 有这种设计, 它没有带来任何好处.
这还只是第一个雷而已. 让我们看第二个雷
"The default value is evaluated only once."
就是这个默认值不但会“超前计算”, 而且还会被 cache 住.
原po的问题就是这个特性造成的.
推文里一直说因为原po的默认值[]是mutable,
因此如何如何, 这里给一个 immutable 一样会雷到人的例子
1 import random
2
3 def func(a=random.random()):
4 return a
5
6 x = func()
7 y = func()
请问 x 和 y 会相等吗?
正常人类: 机率很小, 除非刚好两次随机抽到一样的数
Python : 保证 x,y 完全一样!
为什么? 因为 Python 会把 random.random() 的值记录在 func.__defaults__ 里
所以你看到了, random.random() 的回传值 float 是 immutable 又如何? 还不是雷人.
雷就雷吧, 有什么其他的好处吗? 抱歉, 只有特定场合需要建立变量的副本时很方便,
但这属于“歪打正著”和“先射箭再画靶”的劣招, 带来方便的同时带来更多混淆.
这两个雷就是设计缺陷, 没什么好争的, 从 python 2 或更早以前就存在,
Guido 本人也承认是设计缺陷
https://twitter.com/gvanrossum/status/1014524798850875393
正确的实作应该是“默认值仅在 函数呼叫且该参数被省略时 计算”
这是 Python 少数几个地雷之一, 小心并正确地避开就是了
官方建议“默认值不要用mutable object”也是如此用意.
但这是不够的, 你还要保证这个 default value 建立时没有其他“副作用”
另外 其实不建议初学者直接往所谓的“底层”去看, 那不是原因, 那只是实作而已.
就像写C++也不要没事就看组语, 写Java不要没事就去读bytecode
应先以更宏观角度理解现象的本质
你以为把“基础”研究透了, 实际上你只是研究了一个二十年前的错误而已,
甚至无法意识到这究竟是不是个错误.
作者: pmove (金疾柠檬)   2020-04-06 15:07:00
第8行work(DB)可以拿掉,仍然会执行第4行根第7行两次
楼主: bibo9901 (function(){})()   2020-04-06 15:14:00
我当然知道可以拿掉 我是以人类的角度写正常的程式
作者: pmove (金疾柠檬)   2020-04-06 15:39:00
你举例的情形是argument assign一个function,function虽然会return值,但跟argument直接assign mutable/immutable时的情况,是不一样的。原Po是问assign None objet, None object is a unique, immutable object.
楼主: bibo9901 (function(){})()   2020-04-06 15:47:00
我哪有给一个function, 我是给function的回传值 OK?原po的问题还结合了 variable scope 的问题难道random.random()就不是unique&immutable object XD再说unique object是啥? 每个object都嘛unique..
作者: pmove (金疾柠檬)   2020-04-06 16:31:00
Unique 是指None object的值只有None, 不像int可以有1 或者4...我觉得b大有点超译原po所要问的,只有我这样感觉吗?
作者: yushes920179 (乐冰)   2020-04-07 08:33:00
我觉得你说的很好懂 不过有必要这么派吗
作者: TuCH (谬客)   2020-04-07 15:57:00
学到了 重点就是"参数的默认值于函数定义时计算"雷一跟雷二跟其他例子都是这个的延伸
作者: pmove (金疾柠檬)   2020-04-07 16:36:00
补充一点,如果函数定义在缩排里面,但是此函数的参数直接函数。那么此参数就不会立刻有值了。
作者: Starcraft2 (来自星海的你)   2020-04-09 01:54:00
推详细说明
作者: s860134 (s860134)   2020-04-15 22:55:00
"参数的默认值于函数定义时计算" 重点只要你默认值不要存值,存 function pointer 就解决了~e.g. property, 或是 callable object像本篇例子 check_missing 可以置换成一个有 __call__或 __getattr__ 的自定义 class 即可达成想要的效果

Links booklink

Contact Us: admin [ a t ] ucptt.com