Mutable objects as default arguments in python

2

I have a question with the passage of arguments in python , when mutable data structures are passed as arguments, for example a lista , and another simple argument, in the following way:

def  arguments_mutables(arg, arg1=[]):
    """returns arguments mutables"""
    arg1.append("Arguments")


print(arguments_mutables(1))
print(arguments_mutables(2))
print(arguments_mutables(3))

It turns out that when passing as an argument the value for arg each call increases a indice for the lista , you can explain me why the índice is saved for each call to the function.

    
asked by julian salas 26.03.2016 в 17:59
source

2 answers

5

Javier's response is very good but only solves your specific case, maybe what you really want to know is that the lists work by reference .

What this means is that if you create a list l1 :

>>> l1 = []

Actually what you are doing is passing a reference to the object. Therefore if you then create another list based on the first one:

>>> l2 = l1

Now l2 has the reference to the same object. To understand it better, consider the following example:

>>> l1 = [1, 2, 3]
>>> l2 = l1
>>> l2.append(4)
>>> l1
[1, 2, 3, 4]
>>> l2
[1, 2, 3, 4]
>>> l1 is l2 # Tienen referencia al mismo objeto
True

As you can see, adding an element to l2 also happens the same for l1 since both variables have the reference to the same list.

The same thing happens with dictionaries:

>>> d1 = {'nombre': 'Cesar'}
>>> d2 = d1
>>> d2['apellido'] = 'Bustios'
>>> d1
{'apellido': 'Bustios', 'nombre': 'Cesar'}
>>> d2
{'apellido': 'Bustios', 'nombre': 'Cesar'}
>>> d1 is d2 # Tienen referencia al mismo objeto
True

What happens when executing your script, is that in the definition of your function arguments_mutables , you are creating in arg1 the reference to a list, that is why the successive calls to this function increases the size of the list since in fact the reference to the same object is being used.

If you want to keep the original object when applying an operation, what you have to do is pass it a copy of the object. In the case of lists this is very simple:

>>> l1 = [1, 2, 3, 4, 5]
>>> l2 = l1[:] # Una copia, un objeto nuevo
>>> l2.extend([6, 7, 8, 9, 10])
>>> l1
[1, 2, 3, 4, 5]
>>> l2
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> l1 is l2 # No tienen referencia al mismo objeto
False

In the case of dictionaries, you can use the copy method:

>>> d1 = {'nombre': 'Cesar'}
>>> d2 = d1.copy() # Una copia, un objeto nuevo
>>> d2['apellido'] = 'Bustios'
>>> d1
{'nombre': 'Cesar'}
>>> d2
{'apellido': 'Bustios', 'nombre': 'Cesar'}
>>> d1 is d2 # No tienen referencia al mismo objeto
False

For more complex dictionaries you should use copy.deepcopy .

If you have a function, you can also copy it to keep the original object. Consider the following function:

import random

def extendedora(lista):
    lista.append(random.randint(6, 10))
    return lista

Now, the first case, using the same list:

>>> lista_original = [1, 2, 3, 4, 5]
>>> lista_extendida = extendedora(lista_original)
>>> lista_original
[1, 2, 3, 4, 5, 9]
>>> lista_extendida
[1, 2, 3, 4, 5, 9]
>>> lista_original is lista_extendida
True

Using a copy to maintain the original list:

>>> lista_original = [1, 2, 3, 4, 5]
>>> lista_extendida = extendedora(lista_original[:])
>>> lista_original
[1, 2, 3, 4, 5]
>>> lista_extendida
[1, 2, 3, 4, 5, 6]
>>> lista_original is lista_extendida
False
    
answered by 28.03.2016 в 15:07
2
  

The default values are evaluated from left to right when the   definition of the function is executed. This means that the expression   it is evaluated only once, when the function is defined, and that same   "pre-computed" value is used for each call. This is especially   important to understand when a default argument is an object   mutable, such as a list or a dictionary: if the function modifies   the object (for example by adding an item to the list), the value   defect in effect is modified. This is usually not the intention.

     

Function Definitions

For example, the following function accumulates the arguments passed to it in successive calls:

def f(a, L=[]):
  L.append(a)
  return L

print(f(1))
print(f(2))
print(f(3))

# Esto imprime
# [1]
# [1, 2]
# [1, 2, 3]

If you do not want the default value to be shared between successive calls to the function, you can write it like this:

def f(a, L=None):
  if L is None:
    L = []
  L.append(a)
  return L

print(f(1))
print(f(2))
print(f(3))

# Esto imprime
# [1]
# [2]
# [3]
    
answered by 26.03.2016 в 23:30