1

As far as I know, python passes parameter as reference. I have following code

def func(arr):
    print(arr)
    if arr == [] :
        return
    for i in range(len(arr)):
        arr[i] *= 2
    func(arr[1:])

r = [1,1,1,1]
func(r)
print(r)

I would expect the output to be [2,4,8,16].

Why it outputs [2,2,2,2] as if the reference only works for one level of recursion?

maybe 'arr[1:]' always creates a new object? If that is the case, is there any way to make arr[1:] work?

Ruoyun Huang
  • 173
  • 10

5 Answers5

2

You've asked a couple different questions, let's go through them one by one.

As far as I know, python passes parameter as reference.

The correct term for how python passes it's arguments is "pass py assignment". It means that parameters inside the function behave similar to how they would if they had been directly assigned with an = sign. (So, mutations will reflect across all references to the object. So far so good)

Sidenote (skip if this is confusing): For all intents and purposes, the distinction of "pass by assignment" is important because it abstracts away pass by value vs pass by reference, concepts that are not exposed in python directly. If you wish to know how the underlying mechanism works, it's actually a pass by value, but every value itself is a reference to an object (equivalent to a first level pointer in C speak). We can see why it's easier and important initially not to worry about this particular abstraction, and use "pass by assignment" as the more intuitive explanation.

Next,

maybe 'arr[1:]' always creates a new object?

Correct, slicing always creates a shallow copy of the list. docs

If that is the case, is there any way to make arr[1:] work?

Not directly, but we can use indexes instead to build a solution that works and gives us the output you desire. Just keep track of a starting index while doing the recursion, and increment it as you continue recursing.

def func(arr, start=0):
    print(arr)
    if arr[start:] == [] :
        return
    for i in range(start, len(arr)):
        arr[i] *= 2
    func(arr, start + 1)

r = [1,1,1,1]
func(r)
print(r)

Output:

[1, 1, 1, 1]
[2, 2, 2, 2]
[2, 4, 4, 4]
[2, 4, 8, 8]
[2, 4, 8, 16]
[2, 4, 8, 16]
Paritosh Singh
  • 6,034
  • 2
  • 14
  • 33
1

You do slice arr[1:] and as the result it creates new list. That's why you got such result, in future I would not recommend you do such thing it's implicit and hard to debug. Try to return new value instead of changing it by reference when you work with functions

For example like this:

def multiplier(arr):
    return [
        value * (2 ** idx)
        for idx, value in enumerate(arr, start=1)
    ]

result = multiplier([1, 1, 1, 1])
print(result) # [2, 4, 8, 16]
alex2007v
  • 1,230
  • 8
  • 12
0

Slicing a list will create a new object (as you speculated), which would explain why the original list isn't updated after the first call.

Mureinik
  • 297,002
  • 52
  • 306
  • 350
0

Yes, arr[1:] create a new object. You can pass the index to indicate the starting index.

def func(arr, start_idx):
    print(arr)
    if arr == [] :
        return
    for i in range(start_idx, len(arr)):
        arr[i] *= 2
    func(arr, start_idx + 1)

r = [1,1,1,1]
func(r, 0)
print(r)
cwliang
  • 135
  • 11
0

You can use this. The variable s represents start and e represents end, of the array.

def func(arr,s,e):
    print(arr) #comment this line if u dont want the output steps
    if s>=e:
        return
    for i in range(s,e):
        arr[i] *= 2
    func(arr,s+1,e)

r = [1,1,1,1]
func(r,0,len(r))
print(r)
shubhamr238
  • 1,226
  • 14
  • 22