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]