作者bibo9901 (function(){})()
看板Python
标题Re: [问题] None在def中的变化
时间Mon Apr 6 14:24:06 2020
: 推 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
应先以更宏观角度理解现象的本质
你以为把「基础」研究透了, 实际上你只是研究了一个二十年前的错误而已,
甚至无法意识到这究竟是不是个错误.
--
※ 发信站: 批踢踢实业坊(ptt.cc), 来自: 172.103.227.117 (加拿大)
※ 文章网址: https://webptt.com/cn.aspx?n=bbs/Python/M.1586154248.A.888.html
※ 编辑: bibo9901 (172.103.227.117 加拿大), 04/06/2020 14:25:17
※ 编辑: bibo9901 (172.103.227.117 加拿大), 04/06/2020 14:59:30
※ 编辑: bibo9901 (172.103.227.117 加拿大), 04/06/2020 14:59:59
※ 编辑: bibo9901 (172.103.227.117 加拿大), 04/06/2020 15:01:07
※ 编辑: bibo9901 (172.103.227.117 加拿大), 04/06/2020 15:02:11
※ 编辑: bibo9901 (172.103.227.117 加拿大), 04/06/2020 15:13:05
1F:→ pmove: 第8行work(DB)可以拿掉,仍然会执行第4行根第7行两次 04/06 15:07
※ 编辑: bibo9901 (172.103.227.117 加拿大), 04/06/2020 15:14:05
2F:→ bibo9901: 我当然知道可以拿掉 我是以人类的角度写正常的程式 04/06 15:14
※ 编辑: bibo9901 (172.103.227.117 加拿大), 04/06/2020 15:17:53
※ 编辑: bibo9901 (172.103.227.117 加拿大), 04/06/2020 15:19:28
3F:→ pmove: 你举例的情形是argument assign一个function, 04/06 15:39
※ 编辑: bibo9901 (172.103.227.117 加拿大), 04/06/2020 15:39:52
4F:→ pmove: function虽然会return值,但跟argument直接assign mutable/ 04/06 15:40
※ 编辑: bibo9901 (172.103.227.117 加拿大), 04/06/2020 15:40:43
5F:→ pmove: immutable时的情况,是不一样的。原Po是问assign None obje 04/06 15:41
6F:→ pmove: t, None object is a unique, immutable object. 04/06 15:41
※ 编辑: bibo9901 (172.103.227.117 加拿大), 04/06/2020 15:45:25
7F:→ bibo9901: 我哪有给一个function, 我是给function的回传值 OK? 04/06 15:47
8F:→ bibo9901: 原po的问题还结合了 variable scope 的问题 04/06 15:48
※ 编辑: bibo9901 (172.103.227.117 加拿大), 04/06/2020 15:50:36
9F:→ bibo9901: 难道random.random()就不是unique&immutable object XD 04/06 16:03
10F:→ bibo9901: 再说unique object是啥? 每个object都嘛unique.. 04/06 16:04
11F:→ pmove: Unique 是指None object的值只有None, 不像int可以有1 或者 04/06 16:31
12F:→ pmove: 4... 04/06 16:31
13F:→ pmove: 我觉得b大有点超译原po所要问的,只有我这样感觉吗? 04/06 16:46
14F:推 yushes920179: 我觉得你说的很好懂 不过有必要这麽派吗 04/07 08:33
15F:推 TuCH: 学到了 重点就是"参数的预设值於函数定义时计算" 04/07 15:57
16F:→ TuCH: 雷一跟雷二跟其他例子都是这个的延伸 04/07 15:59
17F:→ pmove: 补充一点,如果函数定义在缩排里面,但是此函数的参数直接 04/07 16:36
18F:→ pmove: 函数。那麽此参数就不会立刻有值了。 04/07 16:37
19F:推 Starcraft2: 推详细说明 04/09 01:54
20F:推 s860134: "参数的预设值於函数定义时计算" 重点 04/15 22:55
21F:→ s860134: 只要你预设值不要存值,存 function pointer 就解决了~ 04/15 22:57
22F:→ s860134: e.g. property, 或是 callable object 04/15 22:58
23F:→ s860134: 像本篇例子 check_missing 可以置换成一个有 __call__ 04/15 23:00
24F:→ s860134: 或 __getattr__ 的自定义 class 即可达成想要的效果 04/15 23:02