List modification problem within a function (python)

Asked

Viewed 132 times

2

def f(i):
    return i + 2
def g(i):
    return i > 5
def aplicacao(L, f, g):    
    lista_f = [f(i) for i in L]
    lista_g = [g(i) for i in lista_f]
    L = [L[k] for k in range(len(L)) if lista_g[k] == True]    
    if len(L) == 0:
        return -1
    else:    
        return L      
    
L = [0, -10, 5, 6, -4]
print(aplicacao(L, f, g))

The application function(L, f, g) takes a list L as parameter and applies the functions f and g, returning a new list L with certain values that depends on the result of f and g.

My question is related to print. When I do print(application(L, f, g)) i get [0, -10, 5, 6, -4], that is, the original input, without the function has modified my list. However, when I rewrote another simpler function, for example:

def func(l):
    return l.append(5)
l = [1, 2, 3, 4]
func(l)
print(l)

I got a modified list, in this case [1, 2, 3, 4, 5].

Can anyone explain why? Thank you.

  • I managed to solve the problem by modifying the line L = [L[k] for k in range(len(L)) if lista_g[k] == True] for L[:] = [L[k] for k in range(len(L)) if lista_g[k] == True]. Apparently so I’m replacing the elements list L. However, I still can’t understand why it works.

1 answer

2

Passing parameters by value or by reference

The term "parameter" is used to indicate the names in parentheses in the function declaration. The term "argument" refers to the variables or literal values passed when a line of code calls the function.

It is very common to see programmers using only the term "parameter", without all this scientific rigor.

To make it easier, in this answer, I will use the term "parameter" to indicate both.

In programming languages, the passing of parameters occurs by value or by reference. Some languages have other forms, but all end up being a combination of these two types.

If the passage is by value, a copy of the original variable is passed to the function. Even if the value of the parameter is changed within the function, the original value (outside the function) has not been changed when the function ends.

If the passage is by reference, a reference to the memory address of the original variable is passed to the function. Thus, any change in the value of the parameter within the function becomes permanent in the original variable, even after the function terminates.

In Python, parameter passing is done by value, but... in Python, everything is an object.

Therefore, the value passed to the function is not the value of the object, but a copy of the reference to the object, which is in the call stack (rundown).

You don’t need to understand how this memory management works now. You just need to know that, if you try to change the value of the object within the function, a new reference is created within the function and the link to the original object is lost.

Take this example:

def mudar(numero, lista):
    numero = 5
    lista = ['nova']
    print('1 - ', numero)
    print('2 - ', lista)

n = 1
l = ['original']
mudar(n, l)
print('3 - ', n)
print('4 - ', l)

The function mudar() receives a reference for objects n and l in the parameters numero and lista. Then the function changes the values of these parameters.

However, the link to the original objects is lost at this time and the original values are preserved. This is perceived when the function finishes running.

The result is as follows:

1 -  5
2 -  ['nova']
3 -  1
4 -  ['original']

It sounds complicated, but it’s easy to understand if you consider the following:

Within a function, the original object can never receive a new assignment, i.e., it cannot be replaced. Any attempt to modify the value of an object passed as a parameter within a function results in the creation of a new object.

So it is never possible to modify the contents of a passed object to a function?

It is, but without replacing the original object.

This means that if the object is immutable, such as a string, an integer, or a tuple, the original value of the object before the call cannot be changed within the function.

But if the object is changeable, such as a list or dictionary, it is possible modify the items inside the object function. The object will remain the same, but with the status changed after executing the function.

Here’s how, in this example:

def acrescentar(numero, lista):
    numero += 5
    lista.append('novo item')

n = 1
l = ['original']
acrescentar(n, l)
print(n)
print(l)

The function acrescentar() sum 5 to the value of the number passed. At this point, when trying to change the value of an immutable object (int), a new object is created, with the value 6. This object is lost when the function ends.

In the case of the list, there is no new assignment for the variable, but a change of the original list, using the function append() to add an element.

This modification is possible and becomes permanent, even after the function ends.

The result of this programme is as follows::

1
['original', 'novo item']

The change of the list within the function, seen in this last example, is known in programming as Side Effect.

This means that the function did not only perform a computational task and returned a value. She left the system in a different state than it was before the function was executed.

In the case of your program, this is what is happening. On the line:

L = [L[k] for k in range(len(L)) if lista_g[k] == True]

you replace the reference to the variable L within the function. Therefore, the value of the list L outside the function remains the same.

The print() at the end of your original code even works, because you return the modified L variable in the function. However, you do not overwrite the original L variable with this result. It is only printed and then lost.

A simple and quick solution would be to assign the function return to L. Instead of:

L = [0, -10, 5, 6, -4]
print(aplicacao(L, f, g))

You could do:

L = [0, -10, 5, 6, -4]
L = aplicacao(L, f, g)
print(L)

L[:] = [L[k] for k in range(Len(L)) if lista_g[k] == True]. Apparently so I’m replacing the elements of the L-list.

Yes, you are assigning the list comprehension on the right side to a slice of the list L. In this case, as you did not enter the initial and final indexes, the slice corresponds to the entire list.

To avoid confusion, you can simplify the function aplicacao(), returning the expected value without this assignment to the original variable. For example:

def aplicacao(L, f, g):    
    lista_f = [f(i) for i in L]
    lista_g = [g(i) for i in lista_f]
    return [L[k] for k in range(len(L)) if lista_g[k] == True] or -1

This way it is clear that the list passed is only used for processing and the returned value needs to be assigned to a variable in the code that calls the function.

I hope I’ve helped.

Source: Adapted from https://vaiprogramar.com/como-declarar-uma-funcao-em-python/#parametros_funcao. Accessed on 6/7/2020.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.