Function Changes List Values and Not Variable Values in Python

Function changes list values and not variable values in Python

Python variables contain pointers, or references, to objects. All values (even integers) are objects, and assignment changes the variable to point to a different object. It does not store a new value in the variable, it changes the variable to refer or point to a different object. For this reason many people say that Python doesn't have "variables," it has "names," and the = operation doesn't "assign a value to a variable," but rather "binds a name to an object."

In plusOne you are modifying (or "mutating") the contents of y but never change what y itself refers to. It stays pointing to the same list, the one you passed in to the function. The global variable y and the local variable y refer to the same list, so the changes are visible using either variable. Since you changed the contents of the object that was passed in, there is actually no reason to return y (in fact, returning None is what Python itself does for operations like this that modify a list "in place" -- values are returned by operations that create new objects rather than mutating existing ones).

In plusOne2 you are changing the local variable a to refer to a different integer object, 3. ("Binding the name a to the object 3.") The global variable a is not changed by this and continues to point to 2.

If you don't want to change a list passed in, make a copy of it and change that. Then your function should return the new list since it's one of those operations that creates a new object, and the new object will be lost if you don't return it. You can do this as the first line of the function: x = x[:] for example (as others have pointed out). Or, if it might be useful to have the function called either way, you can have the caller pass in x[:] if he wants a copy made.

Why is a list variable sometimes not impacted by changes in function as I thought python3 works on pass by reference with list variables?

Your ultimate problem is confusing (in-place) mutation with rebinding (also referred to somewhat less precisely as "reassignment").

In all the cases where the change isn't visible outside the function, you rebound the name inside the function. When you do:

name = val

it does not matter what used to be in name; it's rebound to val, and the reference to the old object is thrown away. When it's the last reference, this leads to the object being cleaned up; in your case, the argument used to alias an object also bound to a name in the caller, but after rebinding, that aliasing association is lost.

Aside for C/C++ folks: Rebinding is like assigning to a pointer variable, e.g. int *px = pfoo; (initial binding), followed later by px = pbar; (rebinding), where both pfoo and pbar are themselves pointers to int. When the px = pbar; assignment occurs, it doesn't matter that px used to point to the same thing as pfoo, it points to something new now, and following it up with *px = 1; (mutation, not rebinding) only affects whatever pbar points to, leaving the target of pfoo unchanged.

By contrast, mutation doesn't break aliasing associations, so:

name[1] = val

does rebind name[1] itself, but it doesn't rebind name; it continues to refer to the same object as before, it just mutates that object in place, leaving all aliasing intact (so all names aliasing the same object see the result of the change).

For your specific case, you could change the "broken" functions from rebinding to aliasing by changing to slice assignment/deletion or other forms of in-place mutation, e.g.:

def func3(_in):
# _in = list() BAD, rebinds
_in.clear() # Good, method mutates in place
del _in[:] # Good, equivalent to clear
_in[:] = list() # Acceptable; needlessly creates empty list, but closest to original
# code, and has same effect

def func5_no_ret(_src, _dest1, _dest2):
# BAD, all rebinding to new lists, not changing contents of original lists
#_dest1 = [val for val in _src[0:len(_src):2]]
#_dest2 = [val for val in _src[1:len(_src):2]]
#_src = list()

# Acceptable (you should just use multiple return values, not modify caller arguments)
# this isn't C where multiple returns are a PITA
_dest1[:] = _src[::2] # Removed slice components where defaults equivalent
_dest2[:] = _src[1::2] # and dropped pointless listcomp; if _src might not be a list
# list(_src[::2]) is still better than no-op listcomp
_src.clear()

# Best (though clearing _src is still weird)
retval = _src[::2], _src[1::2]
_src.clear()
return retval

# Perhaps overly clever to avoid named temporary:
try:
return _src[::2], _src[1::2]
finally:
_src.clear()

Why can a function change an objects variable but not a variable

Python passes variables by Value, but objects by Reference.

So if you modify a variable, you modify your local copy, not the original; if you modify an object, you modify the original.

Changing the passed-in list within a function

Assigning to a name never changes the value previously referenced by the list. This is true even without invoking function parameters.

>>> x = [1,2,3]
>>> y = x
>>> y = [4,5,6]
>>> x
[1, 2, 3]

Changing what y refers to does not alter what x refers to.


In your second example, you are invoking a mutating method on the list referenced by the parameter. Both my_list and in_list refer to the same list, so it any changes made to the list via either reference are visible from either reference.

See https://nedbatchelder.com/text/names.html for a more in-depth discussion of how references work in Python.

Python not saving variable values when they are changed inside a function

Python can read from global variables inside a function, but can't assign them without some extra work. In general, when you want to use a global variable, it's a good idea to make it explicit by using the global keyword in your function:

my_global_var = 0

def some_function():
global my_gobal_var

my_global_var = 10

print(my_global_var) # it prints 10

somefunction() # modifies the global var

print(my_global_var) # now it prints 10

Python: Keep changes on a variable made within a function

This is an example of passing variables to functions by value. By default, when you pass a variable to a function in Python, it is passed by value.

What it means is, that a new variable with a new scope is created with the same value of x. So, any change that happens to the new x is not reflected to the x outside the function's scope.

If you want to get the value from the function back, you can use the return statement (as you have used). return returns the value back from the function. However, in your example there is no variable to receive it. Hence, it is lost.

You would have to call the function as x = test(x). This ensures that x receives the value back from the function.



Related Topics



Leave a reply



Submit