這是第一次提問,所以請強調我的錯誤。
我正在研究一些 Leetcode,并在 Python 中遇到了一種行為(與問題無關),我無法弄清楚也無法通過谷歌搜索。這尤其困難,因為我不確定我缺乏理解是否在:
- 遞回
- Python中的
=運算子或一般的變數賦值 - 或 Python 的傳遞共享行為
- 或者完全是別的東西
這是簡化的代碼:
class Holder:
def __init__(self, val=0):
self.val = val
class Solution:
def runThis(self):
holder = Holder()
self.diveDeeper(holder, 5)
return
def diveDeeper(self, holder, n):
if n==0:
return 1
# 1) Doesn't result in mutation
holder.val = self.diveDeeper(holder, n-1)
# 2) Also doesn't result in mutation
# holder.val = holder.val self.diveDeeper(holder, n-1)
# 3) !! Results in mutations
# returnVal = self.diveDeeper(holder, n-1)
# holder.val = returnVal
print(holder.val)
return 1
a = Solution()
a.runThis()
所以是的,我的主要困惑來源是(1)和(3)在語意上與我看起來相同,但會導致兩個完全不同的結果:
================ RESTART: Case 1 ===============
1
1
1
1
1
>>>
================ RESTART: Case 3 ===============
1
2
3
4
5
>>>
從(2)開始,它似乎與 =操作員無關,為了簡潔起見,我沒有包括我嘗試過的數十種變體,但到目前為止它們都沒有給我任何線索。非常感謝任何正確方向的指示(特別是如果我在作業面試中被蒙蔽了眼睛)
PS:如果這是相關的,我使用的是 Python 3.8.2
uj5u.com熱心網友回復:
你讓我有點掙扎,但答案很簡單。讓我重新解釋一下為什么會這樣。
holder.val = holder.val self.diveDeeper(holder, n - 1) # prints 1 1 1 1 1
holder.val = self.diveDeeper(holder, n - 1) holder.val # prints 1 2 3 4 5
我希望你現在看到發生了什么 - 如果 =它評估為第一個變體。在每個遞回步驟中,holder.val執行該行時將為 0。這就是為什么我們將分配 5 次holder.val = 0 1。
更改順序后,我們首先進行變異holder.val,然后使用它來計算新的。通過參考傳遞按預期作業。
uj5u.com熱心網友回復:
在 Python 中,如果您有expression1() expression2(),expression1()則首先評估。
所以 1 和 2 真的等同于:
left = holder.val
right = self.diveDeeper(holder, n - 1)
holder.val = left right
現在,holder.val僅在遞回呼叫之后修改,但您使用遞回呼叫之前的值,這意味著無論迭代如何,left == 0.
您的解決方案 3 相當于:
right = self.diveDeeper(holder, n - 1)
left = holder.val
holder.val = left right
所以遞回呼叫是在left = holder.val評估之前進行的,這意味著left現在是上一次迭代的總和的結果。
這就是為什么你必須小心可變狀態,你必須完全理解操作的順序。
uj5u.com熱心網友回復:
查看位元組碼dis:
>>> from dis import dis
>>> dis('a.b = c()')
1 0 LOAD_NAME 0 (a)
2 DUP_TOP
4 LOAD_ATTR 1 (b)
6 LOAD_NAME 2 (c)
8 CALL_FUNCTION 0
10 INPLACE_ADD
12 ROT_TWO
14 STORE_ATTR 1 (b)
16 LOAD_CONST 0 (None)
18 RETURN_VALUE
>>> dis('''r = c()
... a.b = r''')
1 0 LOAD_NAME 0 (c)
2 CALL_FUNCTION 0
4 STORE_NAME 1 (r)
2 6 LOAD_NAME 2 (a)
8 DUP_TOP
10 LOAD_ATTR 3 (b)
12 LOAD_NAME 1 (r)
14 INPLACE_ADD
16 ROT_TWO
18 STORE_ATTR 3 (b)
20 LOAD_CONST 0 (None)
22 RETURN_VALUE
一個明顯的區別是前者在呼叫函式之前已經加載了的值,a.b每次遞回都會為0。因為數字是不可變的物件,所以預先加載的數字在每次相加和存盤后都不會改變。后者是a.b呼叫函式后loaded的值,導致a.b每次遞回后更新的值。
uj5u.com熱心網友回復:
對于案例 1。
holder.val = self.diveDeeper(holder, n-1)
這里的初始值為holder.val0。所以實際上holder.val應該等于self.diveDeeper(holder, n-1)
由于此函式僅回傳 1 并holder.val保持區域變數,因此在所有函式呼叫中將保持 0(最初),即
當 N=5 時
holder.vaL = 0 self.diveDeeper(holder, 5)
= 0 (0 self.diveDeeper(holder, 4))
= 0 (0 (0 self.diveDeeper(holder, 3)))
= 0 (0 (0 (0 self.diveDeeper(holder, 2))))
= 0 (0 (0 (0 (0 self.diveDeeper(holder, 1)))))
= 0 (0 (0 (0 (0 1))))
= 1
注意每個函式呼叫中的值不存盤在任何地方
而在情況3
returnVal = self.diveDeeper(holder, n-1)
holder.val = returnVal
在這里,每次呼叫returnVal 的值為 1。即
n = 5, returnVal = self.diveDeeper(holder, 4)
= self.diveDeeper(holder, 3)
= self.diveDeeper(holder, 2)
= self.diveDeeper(holder, 1)
= self.diveDeeper(holder, 0)
= 1
所以我們得到了returnVal = 1。不是 `holder.val 部分
因為holder是一個物件,并且在函式呼叫期間,它會不斷傳遞函式呼叫,所以它將始終保持不變
因此,對于 n=0 時的最后一次呼叫,holder.val = returnValmake holder.val= 1。現在,當它回傳 1 并在 n=1 時進入遞回鏈時,holder.val更新為 1,不再為 0,所以
holder.val = 1 self.diveDeeper(holder, 1)
即所有對holder.val最新通話的更新的參考不再是 0
ie when n = 0, holder.val = 0
n =1, holder.val = holder.val ( value of holder.val when n= 0, ie 0) 1 (1 is value of self.diveDeeper(holder, 0)
n= 2 holder.val = holder.val ( value of holder.val when n= 1, ie 1) 1 (1 is value of self.diveDeeper(holder, 1)
n= 3 holder.val = holder.val ( value of holder.val when n= 2, ie 2) 1 (1 is value of self.diveDeeper(holder, 2)
n= 4 holder.val = holder.val ( value of holder.val when n= 3, ie 3) 1 (1 is value of self.diveDeeper(holder, 3)
n= 5 holder.val = holder.val ( value of holder.val when n= 4, ie 4) 1 (1 is value of self.diveDeeper(holder, 4)
uj5u.com熱心網友回復:
我認為它來自您使用遞回的方式。
當您呼叫holder.val = self.diveDeeper(holder, n-1)持有人時,您傳遞給潛水更深的方法是沒有更新 val 的方法。所以你會呼叫n次diveDeeper函式而不修改持有者val,最后你會回傳1。
您的示例將按如下方式執行:
- 在持有人未修改的情況下呼叫 self.diveDeeper,n = 5;
- 在持有人未修改的情況下呼叫 self.diveDeeper,n = 4;
- 在持有人未修改的情況下呼叫 self.diveDeeper,n = 3;
- 在持有人未修改的情況下呼叫 self.diveDeeper,n = 2;
- 在持有人未修改的情況下呼叫 self.diveDeeper,n = 1;
- 在持有人未修改的情況下呼叫 self.diveDeeper,n = 0;
- 回傳 1
- print holder.val 5 次沒有被修改,因為你上次遞回回傳 1
在(3)中,遞回完成后,當你打開你的呼叫時,你會從最后一次diveDeeper呼叫中得到1,然后將它添加到holder。在下一次展開時,您將執行相同的操作,從而在每次遞回時將您的 val 從 1 遞增。
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/507835.html
上一篇:網格搜索問題的最大遞回深度誤差
下一篇:嵌套串列深度python的總和
