摘要:另外說下,函數(shù)的返回值,也相當于是一次賦值。只不過,這時候是把函數(shù)內(nèi)部返回值所指向的對象,賦值給外面函數(shù)的調(diào)用者輸出函數(shù)結(jié)束后,這個標簽雖然不存在了,但所指向的對象依然存在,就是指向的新對象。
還記得上一次關(guān)于變量作用域文章 :
Crossin:全菊變量和菊部變量zhuanlan.zhihu.com
我們在公眾號(Crossin的編程教室)里做了個問題投票:
def func(m): m[0] = 20 m = [4, 5, 6] return m l = [1, 2, 3] func(l) print("l =", l)
實際的輸出我想大家都嘗試過了吧,應(yīng)該是選項二:
[20, 2, 3]
和80%人想象中的結(jié)果不一樣。
這是為什么呢?
在 Python 的官方文檔 FAQ 里有這樣一句話
Remember that arguments are passed by assignment in Python.
要記住,Python 里的參數(shù)是通過賦值傳遞的。
https://docs.python.org/3/faq/programming.html#how-do-i-write-a-function-with-output-parameters-call-by-reference
所以要弄清楚參數(shù)傳遞,先得弄清 Python 的賦值。
或許在很多人的直觀印象中,變量是一個容器;給變量賦值,就像是往一個存儲的容器中填入一個數(shù)據(jù);再次賦值就是把容器中的數(shù)據(jù)換掉。
然而,
在 Python 中,這種理解是不準確的!
在 Python 中,這種理解是不準確的!
在 Python 中,這種理解是不準確的!
若是想要個形象的類比, Python 中的變量更像是是個標簽;給變量賦值,就是把標簽貼在一個物體上;再次賦值就是把標簽貼在另一個物體上 。
體會下這兩種設(shè)計的差異:
· 前者,變量是一個固定的存在,賦值只會改變其中的數(shù)值,而變量本身沒有改動。
· 后者,變量不存在實體,它僅僅是一個標簽,一旦賦值就被設(shè)置到另一個物體上,不變的是那些物體。
這些“物體”就是 對象 。 Python 中所有東西都是對象 ,包括函數(shù)、類、模塊,甚至是字符串’hello’,數(shù)字1、2、3,都是對象。
用個例子來說明:
a = 1 b = 2 c = 1 # 再次賦值 a = b
在這個代碼里,a 和 c 其實指向的是同一個對象—整數(shù) 1。給 a 賦值為 b 之后,a 就變成了指向 2 的標簽,但 1 和 c 都不會受影響。
示意圖:
更有說服力一點的驗證:
a = 1 print("a", a, id(a)) b = 2 print("b", b, id(b)) c = 1 print("c", c, id(c)) # 再次賦值 a = b print("a", a, id(a))
輸出:
a 1 4301490544 b 2 4301490576 c 1 4301490544 a 2 4301490576
id() 可以認為是獲取一個對象的地址??梢钥闯?,a 和 c 開始其實是同一個地址,而后來賦值之后,a 又和 b 是同一個地址。
每次給變量重新賦值,它就指向了新的地址,與原來的地址無關(guān)了。
回到函數(shù)的調(diào)用上:
Python 里的參數(shù)是通過賦值傳遞的
def fn(x): x = 3 a = 1 fn(a) print(a)
輸出結(jié)果為 1,a 沒有變化。
調(diào)用 fn(a) 的時候,就相當于做了一次 x = a,把 a 賦值給了 x,也就是把 x 這個標簽貼在了 a 的對象上。只不過 x 的作用域僅限于函數(shù) fn 內(nèi)部。
當 x 在函數(shù)內(nèi)部又被賦值為 3 時,就是把 x 又貼在了 3 這個對象上,與之前的 a 不在有關(guān)系。所以外部的 a 不會有任何變化。
把其中的數(shù)值換成其他對象,效果也是一樣的:
def fn(x): x = [4,5,6] a = [1,2,3] fn(a) print(a)
輸出結(jié)果為 [1,2,3],a 沒有變化。(記住這個例子,最后我們還會提到)
那上次的題目又是怎么回事?
我們再來看一個賦值:
a = [1,2,3] print("a", a, id(a)) b = a print("b", b, id(b)) b[1] = 5 print("a", a, id(a)) print("b", b, id(b))
輸出:
a [1, 2, 3] 4490723464 b [1, 2, 3] 4490723464 a [1, 5, 3] 4490723464 b [1, 5, 3] 4490723464
這個是不是好理解一點?b 賦值為 a 后,和 a 指向同一個列表對象。[1] 這個基于 index 的賦值是 list 對象本身的一種操作,并沒有給 b 重新貼標簽,改變的是對象本身。所以 b 指向的還是原來的對象,此對象的改動自然也會體現(xiàn)在 a 身上。同理,b.append(7) 這樣的操作也會是類似的效果。
再來回顧下原問題呢:
def func(m): m[0] = 20 # m = [4, 5, 6] return m l = [1, 2, 3] func(l) print("l =", l)
去掉那句 m=[4,5,6] 的干擾,函數(shù)的調(diào)用就相當于:
l = [1, 2, 3] m = l m[0] = 20
l 的值變成 [20,2,3] 沒毛病吧。而對 m 重新賦值之后,m 與 l 無關(guān),但不影響已經(jīng)做出的修改。
這就是這道題的解答。上次留言里有些同學(xué)已經(jīng)解釋的很準確了。
另外說下, 函數(shù)的返回值 return,也相當于是一次賦值 。只不過,這時候是把函數(shù)內(nèi)部返回值所指向的對象,賦值給外面函數(shù)的調(diào)用者:
def fn(x): x = 3 print("x", x, id(x)) return x a = 1 a = fn(a) print("a", a, id(a))
輸出:
x 3 4556777904 a 3 4556777904
函數(shù)結(jié)束后,x 這個標簽雖然不存在了,但 x 所指向的對象依然存在,就是 a 指向的新對象。
所以,如果你想要通過一個函數(shù)來修改外部變量的值,有幾種方法:
通過返回值賦值
使用全局變量
修改 list 或 dict 對象的內(nèi)部元素
修改類的成員變量
有相當多的教程把 Python 的函數(shù)參數(shù)傳遞分為可變對象和不可變對象(這個概念下次來說)來說明,然后類比到 C++ 的值傳遞和引用傳遞。我很反對這樣去理解:
對于沒有學(xué)過 C++ 的人來說,這個解釋屬于循環(huán)論證,還是沒說清問題。
Python 本來就不存在值傳遞/引用傳遞的概念,這個比較沒有意義。
這個類比實際上是錯誤的。就算類比,也應(yīng)該是相當于 C++ 里的指針值傳遞。
用可變對象/不可變對象來劃分很容易產(chǎn)生誤解,比如我們前面例子中的 x=[4,5,6],它是可變對象,但一樣不影響外部參數(shù)的值。
這點前面貼出的官方文檔里也直說了:
Since assignment just creates references to objects, there’s no alias between an argument name in the caller and callee, and so no call-by-reference per se.
賦值是創(chuàng)建了一份對象的引用(也就是地址),形參和實參之間不存在別名的關(guān)系,本質(zhì)上不存在引用傳遞。
網(wǎng)上很容易搜到“參數(shù)是可變對象就相當于引用傳遞”這種錯誤的理解。也不知道他們是對 Python 的參數(shù)傳遞有什么誤解,還是對C++的引用傳遞有什么誤解。結(jié)果就是,讓很多初學(xué)者從網(wǎng)上看了幾篇教程之后,更糊涂了。
所以呢,找到一個靠譜的教程是非常重要滴
════
其他文章及回答:
如何自學(xué)Python | 新手引導(dǎo) | 精選Python問答 | Python單詞表 | 區(qū)塊鏈 | 人工智能 | 雙11 | 嘻哈 | 爬蟲 | 排序算法 | 我用Python | 高考 | 世界杯
歡迎搜索及關(guān)注: Crossin的編程教室
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/42456.html
摘要:不可變對象不允許對自身內(nèi)容進行修改。因為他們說到不可變對象時用的是賦值,而說到可變對象又用了的索引等方法,這根本是兩碼事?;谶@一設(shè)定,兩者在功能上的最大區(qū)別就是不可變對象可以作為字典的鍵,而可變對象不行。 前陣子我們聊了下函數(shù)的參數(shù)傳遞以及變量賦值的一些內(nèi)容:關(guān)于函數(shù)參數(shù)傳遞,80%人都錯了 簡單回顧下要點: 1. Python 中的變量不是裝有對象的 容器 ,而是貼在對象上的 標簽...
摘要:之前關(guān)于的作用域賦值參數(shù)傳遞,我們接連談了幾篇文章全菊變量和菊部變量關(guān)于函數(shù)參數(shù)傳遞,人都錯了可變對象與不可變對象今天我們依然要就相關(guān)話題繼續(xù)下去。這是由于它們是不可變對象,不存在被修改的可能,所以拷貝和賦值是一樣的。 之前關(guān)于 Python 的作用域、賦值、參數(shù)傳遞,我們接連談了幾篇文章: 全菊變量和菊部變量 關(guān)于函數(shù)參數(shù)傳遞,80%人都錯了 可變對象與不可變對象 今天我們依然要...
閱讀 2331·2021-11-23 10:09
閱讀 2898·2021-10-12 10:11
閱讀 2605·2021-09-29 09:35
閱讀 1346·2019-08-30 15:53
閱讀 2272·2019-08-30 11:15
閱讀 2916·2019-08-29 13:01
閱讀 2300·2019-08-28 18:15
閱讀 3369·2019-08-26 12:13