为什么 Python 中的临时变量会改变此共享传递变量的行为方式?

Ahm*_*fi' 8 python recursion pass-by-reference

第一次在这里提问,所以请强调我的错误。

我正在研究一些 Leetcode,并在 Python 中遇到了一种行为(与问题无关),我无法完全弄清楚也无法通过谷歌搜索。这特别困难,因为我不确定我是否缺乏理解:

  1. 递归
  2. Python 中的运算符+=或一般变量赋值
  3. 或者Python的传递共享行为
  4. 或者完全是别的东西

这是简化的代码:

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()
Run Code Online (Sandbox Code Playgroud)

所以,是的,我的主要困惑来源是(1)和(3)在语义上对我来说看起来相同,但导致了两个完全不同的结果:

================ RESTART: Case 1 ===============
1
1
1
1
1
>>> 
================ RESTART: Case 3 ===============

1
2
3
4
5
>>> 
Run Code Online (Sandbox Code Playgroud)

从(2)来看,它似乎与操作员无关+=,为了简洁起见,我没有包含我尝试过的数十种变体,但到目前为止它们都没有给我任何线索。非常感谢任何正确方向的指示(特别是万一我在工作面试中措手不及,lmao)

PS:如果这是相关的,我正在使用Python 3.8.2

use*_*462 6

你让我有点挣扎,但答案很简单。让我重新解释一下为什么会发生这种情况。

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
Run Code Online (Sandbox Code Playgroud)

我希望你现在明白发生了什么 - 如果+=它评估为第一个变体。在每个递归步骤中,holder.val执行该行时将为 0。这就是为什么我们将分配 5 次holder.val = 0 + 1

改变顺序后,我们首先进行变异holder.val,然后用它来计算新的。通过引用传递按预期工作。


Jas*_*ijn 3

在 Python 中,如果有expression1() + expression2(),expression1()首先被求值。

所以 1 和 2 实际上相当于:

left = holder.val
right = self.diveDeeper(holder, n - 1)
holder.val = left + right
Run Code Online (Sandbox Code Playgroud)

现在,holder.val仅在递归调用之后修改,但您使用递归调用之前的值,这意味着无论迭代如何,left == 0.

您的解决方案 3 相当于:

right = self.diveDeeper(holder, n - 1)
left = holder.val
holder.val = left + right
Run Code Online (Sandbox Code Playgroud)

因此,递归调用是在left = holder.val求值之前进行的,这意味着left现在是上一次迭代的总和的结果。

这就是为什么你必须小心可变状态,你必须完全理解操作的顺序。