object1 += object2 is different object1= object1 + object2 in python(3.8)?

Asked

Viewed 66 times

1

objeto1 += objeto2 and objeto1= objeto1 + objeto2 give different results even though the two forms seem equivalent. Could someone explain to me what I’m not realizing?

class f():
    def __init__(self,caixa=[]):
        self.caixa= caixa

class g(f):
    def __init__(self,caixa=[]):
        super().__init__(caixa)

    def inicializarFs(self, listaFs):
        self.listaFs= listaFs
        self.listaFs[0].caixa= self.listaFs[0].caixa + ['ok']

             

a= f()
b= f()
c= f()
d= g()

d.inicializarFs([a,b])

print(a.caixa)
print(b.caixa)
print(c.caixa)

console output :

['ok']
[]
[]

HOWEVER:

class f():
    def __init__(self,caixa=[]):
        self.caixa= caixa

class g(f):
    def __init__(self,caixa=[]):
        super().__init__(caixa)

    def inicializarFs(self, listaFs):
        self.listaFs= listaFs
        self.listaFs[0].caixa+= ['ok'] # linha modificada em relação a anterior

a= f()
b= f()
c= f()
d= g()

d.inicializarFs([a,b])

print(a.caixa)
print(b.caixa)
print(c.caixa)

console output :

['ok']
['ok']
['ok']
  • https://stackoverflow.com/a/823878/4802649

  • If one of the answers solved your problem, you can choose the one that solved it best (only one of them) and accept it, see here how and why to do it. It is not mandatory, but it is a good practice of the site, to indicate to future visitors that it solved the problem.

2 answers

3


What happens is a combination of two different behaviors.

The first is what is described here and here. Read the links for more details, but summarizing, the operator += modifies the list itself, while lista = lista + outra_lista creates another list and assigns to the variable lista.

One way to see this difference is by assigning the list to the other variable and modifying the original:

x = [1]
copia = x
x += [2]
print(x, copia) # [1, 2] [1, 2]

x = [1]
copia = x
x = x + [2]
print(x, copia) # [1, 2] [1]

In the first case, the list has been modified by the operator +=, therefore so much x how much copia are modified (since both point to the same list).

In the second case, a new list is created and assigned to x. But copia keeps pointing to the original list, and therefore is not modified.


The second behaviour in question is the one described here. Basically, the default argument is evaluated at the time the function is defined. That is, in:

def __init__(self, caixa=[]):

Not a new empty list is created each time __init__ is called. This list is created only once, when the function is defined, and all instances of f will point to that same list.

So in your case, both a.caixa how much b.caixa and c.caixa are pointing to the same list.

Then, in inicializarFs you are changing the caixa of the first element of listaFs (which in this case is a).

When you use +=, is changing the list itself a.caixa. And how a.caixa., b.caixa and c.caixa are pointing to the same list, by changing a.caixa, the lists b.caixa and c.caixa are also affected (after all, all point to the same list).

Already when you do self.listaFs[0].caixa = self.listaFs[0].caixa + ['ok'], is creating a new list and assigning the a.caixa, while b.caixa and c.caixa continue pointing to the original list, which is still empty.


For the lists to be independent (i.e., that a new list is always created for each instance), an alternative is to do what was suggested in one of the questions already linked (more precisely in this answer). Instead of putting the empty list in the function definition, create a new one inside it:

def __init__(self, caixa=None):
    if caixa is None:
        self.caixa = []
    else:
        self.caixa= caixa

Or, more succinctly:

 def __init__(self, caixa=None):
    self.caixa = [] if caixa is None else caixa
  • 1

    hi - I chose to write another answer to incorporate in fact the differences of = and of += - beyond the problem of [] as a standard argument.

  • I see that the problem was only my understanding a little more in depth in the language, but I find a little harmful the conventions of some behaviors in view of the more natural or more intuitive semantics is compromised... Thank you.

  • 1

    One characteristic of Python is that it has no "exceptions" - this behavior was less planned, than being a consequence of how the language works. It is that for those who are beginners can not be aware of how it actually works - the command "def" that creates functions is executed when a file . py is imported - exercising the "def" command creates a "function" object which is an instance of "Functiontype". One of the attributes of a function is its code. Another of the attributes is the standard arguments - the object placed as the standard argumetno becomes an attribute of the Function and is the same in all calls.

  • 1

    (I have incorporated the above comment in my reply - I think in those words it is clear what happens "inside")

1

Your real problem

Your big problem there is that you’re using a list ([]) as a standard argument in the method __init__ - like the line def __init__(self,caixa=[]): is executed only once, this list is created only once, and a single and same list will be used in all instances class f.

To fix this, change your code to:


class f():
    def __init__(self,caixa=None):
        self.caixa= caixa if caixa is not None else [] 

Done - with this change, a new list is created every instance.

(of course the class g needs the same change in the declaration of __init__)

This whole "list be the same every time __init__ is called, it may seem strange, but precisely, a characteristic of Python is that it has no "exceptions" - this behavior was less planned, than being a consequence of how the language works. For those who are beginners, you don’t know how it actually works - the "def" command that creates functions is executed when a file . py is imported - executing the "def" command creates a "Function" object which is an instance of "Functiontype". One of the attributes of a Function is its code. Another of the attributes is the standard arguments - the object placed as the default argument becomes an attribute of the Function and is the same in all calls.

So, yes, the people who are responsible for evolving language know well that it is an anti-intuitive behavior, and it is a trap for anyone who does not study the language in depth - even if one has a lot of practice in other languages - but the option was in the past for not creating an exception in these cases - and today even if you thought differently, it is not possible to change because of the compatibility break. The best business is to understand this feature and use it to your advantage. (And if an IDE did not alert to the use of a list or dictionary as the default argument, it was right to open a bug against the IDE)

The behavior of a = a + b and a += b

The behavior of += is given by the method __iadd__ if it exists in the left object class. If there is no - the __add__ is called (or __radd__ on the right object) - and the result is identical to the use of x = x + y.

By convention also, the __iadd__ when it exists will modify the own object (self) - and the __add__ will create another instance.

So typically the implementation will be something like:

class F:
   def __init__(self, valor):
        self.valor = valor
   def  __add__(self, other):
       return type(self)(valor=self.valor + other.valor)
   def __iadd__(self, other):
       self.valor += other.valor
       return self

In the case of lists, and other mutable objects, that’s exactly what happens. So lista1 += lista2 will modify the list 1 internally, now lista1 = lista1 + lista2 will create a new list that is the concatenation of lists list1 + Lista2 , and saves the result in the name lista1, over-writing the previous reference.

If you have any reference to list1, before the line lista1 = lista1 + lista2, that reference nay will see changes, and will continue pointing to the original "list1".

Understanding your problem based on these two things:

It’s simple: the line self.listaFs[0].caixa= self.listaFs[0].caixa + ['ok'] creates a NEW list that will be a.caixa -and then the objects b and c will still share the same list- but a.caixa is being an independent list.

While the line: self.listaFs[0].caixa+= ['ok'] changes the same instance of the list, which is in the a.caixa -and then, how it remains shared in the instances b and c, you see this modification looking through those instances.

  • I broke my head a lot with these strange behaviors but now this all clarified, thank you.

Browser other questions tagged

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