Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does a temporary variable in Python change how this Pass-By-Sharing variable behaves?

first-time questioner here so do highlight my mistakes.

I was grinding some Leetcode and came across a behavior (not related to the problem) in Python I couldn't quite figure out nor google-out. It's especially difficult because I'm not sure if my lack of understanding is in:

  1. recursion
  2. the += operator in Python or variable assignment in general
  3. or Python's pass-by-sharing behavior
  4. or just something else entirely

Here's the simplified code:

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()

So yeah my main source of confusion is how (1) and (3) look semantically identical to me but results in two completely different outcomes:

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

1
2
3
4
5
>>> 

From (2), it doesn't seem related to the += operator and for brevity, I haven't included the tens of variations I've tried but none of them have given me any leads so far. Would really appreciate any pointers in the right direction (especially in case I get blindsided in job interviews lmao)

PS: In case this is relevant, I'm using Python 3.8.2

like image 257
Ahmad Mudaafi' Avatar asked Jun 17 '26 09:06

Ahmad Mudaafi'


2 Answers

You struggled me a bit but the answer is really simple. Let me rephrase why that happened.

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

I hope you see now what is going on - in case of += it evaluates to first variant. In every recursion step, holder.val will be 0 when executing that line. That's why we will assign 5 times holder.val = 0 + 1.

With changed order, we first mutate holder.val and then use it to calculate new one. The passing by reference works as expected.

like image 114
kosciej16 Avatar answered Jun 19 '26 23:06

kosciej16


In Python, if you have expression1() + expression2(), expression1() is evaluated first.

So 1 and 2 are really equivalent to:

left = holder.val
right = self.diveDeeper(holder, n - 1)
holder.val = left + right

Now, holder.val is only ever modified after the recursive call, but you use the value from before the recursive call, which means that no matter the iteration, left == 0.

Your solution 3 is equivalent to:

right = self.diveDeeper(holder, n - 1)
left = holder.val
holder.val = left + right

So the recursive call is made before left = holder.val is evaluated, which means left is now the result of the sum of the previous iteration.

This is why you have to be careful with mutable state, you got to understand the order of operations perfectly.

like image 34
Jasmijn Avatar answered Jun 19 '26 23:06

Jasmijn



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!