Why does changing items from a list with multiple assignment not work when I use "index()" inline in assignment?

Asked

Viewed 79 times

3

I can swap a and b with this code:

lista = ['a', 'b', 'c']  

item = 'a'        # itens que quero localizar e trocar
outro_item = 'b'  #

indice_item       = lista.index(item)       # obtenho a posição de ambos
indice_outro_item = lista.index(outro_item) #

# tento fazer a troca invertendo os índices
lista[indice_item],lista[indice_outro_item]=lista[indice_outro_item],lista[indice_item]

# deu certo
print(lista[0]) # b
print(lista[1]) # a 
print(lista[2]) # c

Worked: https://ideone.com/2dZvJV


Only if I don’t use the intermediate variables and call index always, but keeping the original logic, the exchange does not happen:

lista = ['a', 'b', 'c']  

item = 'a'  
outro_item = 'b' 

# aqui estou tentando fazer lista[0,1]=lista[1,0] com indexof diretamente:
lista[lista.index(item)],lista[lista.index(outro_item)]= lista[lista.index(outro_item)],lista[lista.index(item)]

# não trocou!
print(lista[0]) # a
print(lista[1]) # b 
print(lista[2]) # c

Why the second code does not work as the first?
https://ideone.com/hs7YOA

  • 2

    The doubt is very interesting, but it is important [Dit] and explain what you thought would happen and what came out different than expected, otherwise it depends on people having to analyze the code to understand what you wanted to do, and then the less experienced users (who would be the ones who would take advantage of the doubt) would not follow.

  • It’s my first question here. I edited it. Did it get clearer?

  • 1

    It had become clearer, but not clear enough. Never publish 2x the same thing, always edit until you solve the problem. By having made 2 posts and removed, the system has already imposed some limits on your account. I tried to save this one by further detailing the problem, but in next questions, when the system allows, you have to take care to avoid new limitations.

  • Sorry. I just joined the community. I’m still knowing how it works. Thanks for the tips.

1 answer

2

According to the documentation, expressions are evaluated from left to right, and in case of assignments, everything that is to the right of the = is evaluated first. That is, in an expression like yours, the order of evaluation is:

expr3, expr4 = expr1, expr2

First he evaluates expr1, afterward expr2, afterward expr3 (and assigns the value of expr1) and then expr4 (and assigns the value of expr2).

In your case, we have:

lista[lista.index(item)], lista[lista.index(outro_item)] = lista[lista.index(outro_item)], lista[lista.index(item)]

That is, first he will evaluate everything to the right of the =. Then first it is evaluated lista[lista.index(outro_item)], which results in 'b' and then lista[lista.index(item)], which results in 'a'. The result of these 2 expressions results in the tuple ('b', 'a').

Then he evaluates the first expression to the left of =, that is lista[lista.index(item)]. It results in lista[0] (since 'a' is at index zero), and this receives the value 'b'. I mean, now the list is ['b', 'b', 'c'].

So when evaluating lista[lista.index(outro_item)], the result will also be lista[0], for index returns the first position the searched element is at. And the first position where it has a 'b' now it’s zero, so lista[0] will receive the value 'a', and the list will again be ['a', 'b', 'c'].

That is why the first code works, because you only search the indexes once at the beginning, and when evaluating the expressions during the assignment these indexes do not change. Already calling index every time, it runs the risk of returning a different value, since the list is changed during the process.


We can see this creating a subclass of list, just to get a better view:

class mylist(list):
    def index_dir(self, i):
        x = super().index(i)
        print(f'index_dir({i})={x}')
        return x
    def index_esq(self, i):
        x = super().index(i)
        print(f'index_esq({i})={x}')
        return x
    def __getitem__(self, i):
        x = super().__getitem__(i)
        print(f'get({i})={x}')
        return x
    def __setitem__(self, i, value):
        print(f'set({i})={value}')
        super().__setitem__(i, value)
        print(f'lista agora é: {self}')

lista = mylist(['a', 'b', 'c'])
item = 'a'
outro_item = 'b' 
lista[lista.index_esq(item)],lista[lista.index_esq(outro_item)] = lista[lista.index_dir(outro_item)],lista[lista.index_dir(item)]

I created 2 methods index to make clear which one is being called to the right and left of the =. The exit is:

index_dir(b)=1
get(1)=b
index_dir(a)=0
get(0)=a
index_esq(a)=0
set(0)=b
lista agora é: ['b', 'b', 'c']
index_esq(b)=0
set(0)=a
lista agora é: ['a', 'b', 'c']

See how to the right of the = it correctly finds the elements, but when making the first assignment, the list is modified to ['b', 'b', 'c'], and by calling index to find the 'b' it returns zero - and so is the element at index zero that gets `'a'.

Now if I do:

indice_item       = lista.index_dir(item)
indice_outro_item = lista.index_dir(outro_item)
lista[indice_item],lista[indice_outro_item]=lista[indice_outro_item],lista[indice_item]

The exit is:

index_dir(a)=0
index_dir(b)=1
get(1)=b
get(0)=a
set(0)=b
lista agora é: ['b', 'b', 'c']
set(1)=a
lista agora é: ['b', 'a', 'c']

Because indexes are only searched once, and during assignments they remain at the same values, so each position gets the correct value.

Browser other questions tagged

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