Re: [問題] None在def中的變化

作者: gmccntzx1 (o.O)   2020-04-04 16:28:44
這個**問題**其實被蠻多人討論過了,但比起爭論它是否是 Python 的設計瑕疵,我認為
深入了解其背後的運作原理是更值得做的事情。
這邊我先以原 PO 所問的這個段內容起頭:
> ... 想請問為什麼 ex2 裡引述預設值改為 None 時,不會發生印出的內容包含前一次
> 呼叫內容,第一次輸出['a']後,result不是已經變成['a']了嗎 ...
簡單版的回答是:
在那個 function 內所用到的 `result` 一開始指向的是 `None`,如同 signature
裡的 `result=None` 所表示。所以在執行 `if result is None:` 時,所得到的結
果是 True ,因此 `result` 就如下一行一樣,被重新指向一個新的 empty list。
(延伸閱讀: name binding, https://nedbatchelder.com/text/names.html )
詳細版的回答:
一開始, `result` 指向的物件是存在 local scope 裡的。而因為該物件是 `None`
,所以 `result` 也就是指向 `None`。而要知道一開始 local scope 內有什麼東西
,你可以在 if 的上一行加上 `print(locals())` 來觀察。
為求接下來撰文方便,我們用官方文件中的例子來說明,請參考下圖:
https://i.imgur.com/yeMxEP9.png
程式執行到 `print(f(1))` 時,`print` 裡的 function call `f(1)` 會先被執
行。而因為 `f` 第一個參數 `a` 所拿到的值是 1,而 `L` 沒有被指定,所以進入
function 後,`locals()` 回傳的內容會是 `{'a': 1, 'L': []}`。
在執行 `L.append(a)` 之後,`L` 這個 list 的內容變成了 `[1]`。但是,記得
前面提到的 name binding 嗎?由於 `L` 指向的正是 local scope 的那個 `L`,
所以如果接著再呼叫一次 `locals()`,回傳的內容會是 `{'a': 1, 'L': [1]}`。
因此執行到 `print(f(2))` 時,由於稍早在 `f` 的 local scope 內的 `L` 已經
被改變了,所以這時候 `print(locals())` 裡看到的 `L` 就是已經被改變的狀態。
(不過使用 `locals()` 來觀察一個 function 被執行時其 local scope 的內容並
不完全適合,**詳細的原因後續會再說明**。)
但是這跟 mutable/immutable object 有關係嗎?以這個例子來說其實不太適合,
讓我們將它稍微改寫成以下兩個版本:
- mutable default argument
https://i.imgur.com/ole5dma.png
- immutable default argument
https://i.imgur.com/f13zzlx.png
這樣一來,就可以很明顯地了解這個問題跟使用 mutable/immutable object 作為
預設值的差別了。
然而,我們知道了 `locals()` 的用處,那是否可以用它來做些有趣的事情呢?
譬如,直接使用 `locals()` 去修改預設值(暫不考慮有傳入 `L` 的情況)?
https://i.imgur.com/Wozmmqy.png
很抱歉,失敗了。原因有點複雜,恕我在此省略。但其實這點在官方文件也有提到
> Note: The contents of this dictionary should not be modified;
> changes may not affect the values of local and free variables used
> by the interpreter.
https://docs.python.org/3/library/functions.html#locals
但有沒有其他辦法去達到這個目的呢?其實還是有的,方法如下
https://i.imgur.com/PT83bOF.png
不過很明顯地,比起這麼做,倒不如用常見的方法:將預設值設為 `None`,然後在函
數內用 if 判斷以重新設定。
稍微扯遠了。這個問題的根本還是需要回到 "參數初始化的方式" 來討論。原因也如同
官方文件所提到的
> ... The default value is evaluated only once ...
https://docs.python.org/3/tutorial/controlflow.html#more-on-defining-functions
還有,如果 `result` 從一開始就沒有被定義在 function signature 裡的話,在
local scope 內就不會 `result`。在這種情況下,便會循著所謂的 LEGB rule (
local, enclosed, global, built-in) 去做 name resolution 。
(延伸閱讀: LEGB rule,
https://sebastianraschka.com/Articles/2014_python_scope_and_namespaces.html )
作者: s860134 (s860134)   2020-04-04 17:47:00
這是 markdown 吧?
作者: pmove (金疾檸檬)   2020-04-04 17:57:00
從底層的byte code講,當然這是真正的因就像講微分題目,從Lim 講起,這當然可以。只是我有點嚇到
作者: jiyu520 (不要鯽魚我)   2020-04-04 18:10:00
學習了 謝謝
作者: shezion (= =)   2020-04-09 00:38:00
好專業的說明,感謝

Links booklink

Contact Us: admin [ a t ] ucptt.com