Does __add__ for a class have to return a different class?

Question

In this exercise, the __add__() method is implemented for the Atom class but returns a Molecule class. Does __add__() always need to return a different class?

Answer

NO. The __add__() method can be implemented to return whatever type or class makes sense for the operation. For this exercise, the addition of two Atom objects returns a Molecule. In some other case, a class may just return a new instance of the same class. The __add__() method is free to return whatever makes sense for the operation being performed.

6 Likes

Pouvez vous m’expliquer ce qui se passe avec les impressions d’attributs.
salt.atoms (type Molecule) est de type list
salt.label (type Atom) est de type list
dans le premier cas, j’imprime l’adresse mĂ©moire, dans le second, j’imprime bien la liste

sodium = Atom(“Na”) #sodium est de type “Atom”
print(sodium.label) # Na # . label est str
chlorine = Atom(“Cl”)
print(chlorine.label) # Cl
salt = Molecule([sodium, chlorine]) # salt est de type “Molecule”
for i in salt.atoms: # salt.atoms est de type list
print(i.label) # Na Cl # i.label est de type str
print(salt.atoms) # !! retourne l’adresse mĂ©moire !!
print(type(salt.atoms)) # type list pourtant
salt = Atom([“Na”,“Cl”]) # salt est de type “Atom”
print(salt.label) # salt.label est de type List
print(type(salt.label)) # type list - imprime la liste [‘Na’,‘Cl’]

yes, otherwise it’s an iterative definition

Are you disagreeing with the explanation offered by @ajaxninja66418 above? What is an iterative definition? Could you cite an example?

I’m totally lost now


Is that a question. Could you be a bit more specific?

The bottom line is that the + operator is defined fairly intuitively for both numbers and strings, but if you’d like to somehow make use of it in another context, the __add__ method provides a way for you to do so.

3 Likes

I am not an expert but i do not agree to the solution of the add exercise. The definition should be:

def add(self, other):
return Molecule([self.label, other.label])

so then when you print salt.atoms you get [‘Na’, ‘Cl’]

4 Likes

I’m no expert either but i agree with you. You get the same answer as SOLUTION using this code. Also, this code is similar to the example that we are shown in the lesson add.

def add(self, other):
return Molecule([self.label, other.label])

1 Like

The idea behind the dunder method, __add__() is so we don’t have to call on the method explicitly. Just using the + (plus sign) is enough to invoke it.

sodium = Atom("Na")
chlorine = Atom("Cl")
salt = sodium + chlorine
print (f"{salt.atoms[0].label}{salt.atoms[1].label}")
# NaCl
8 Likes

I modified code a bit and it now returns Nacl which make more sense . instead of Atoms address

class Atom:
  def __init__(self, label):
    self.label = label
  def __add__(self, other):
    return Molecule([self.label, other.label])
  
class Molecule:
  def __init__(self, atoms):
    if type(atoms) is list:
	    self.atoms = atoms
  def __repr__(self):   #now I see how usefull repr method could be
    sum = ''
    for atom in self.atoms:
      for char in atom:
        sum += char
    return sum
          
sodium = Atom("Na")
chlorine = Atom("Cl")

print(sodium + chlorine)  # return Nacl Instead of original code returns <__main__.Atom object at 0x7f3cce61e780>, <__main__.Atom object at 0x7f3cce61e9e8>]

4 Likes

Do interpreter parse + operator here to something like sodium.add(chlorine) ? when add used?

The interpreter sees the + operator between two Atom objects so looks for an __add__() method on their instance. It takes the first in the expression as self and the other, which is also an instance, as other. We see above they are not really added, nor even concatenated, just written into a Molecule instance as a list of Atom instances. That instance is then returned. Now we are able to represent that object instance with its __repr__() method.

Aside

The Molecule instance is just a list of Atom objects, each with a label attribute. Below we unpack on the assumption the molecule is a compound of just two atoms:

  def __repr__(self):
    a, b = self.atoms               # unpack
    return f"{a.label}{b.label}"    # return formatted string

Of course it won’t be much good for compounds of more than two elements so iteration will be needed. We won’t need to iterate over characters, though, just the atoms attribute


    def __repr__(self):
      s = ""
      for x in self.atoms:
        s += x.label
      return s
salt = sodium + chlorine
print (salt)
#  NaCl
3 Likes

link
can you help me to understand why my example does not work?

class Atom:
  def __init__(self, label):
    self.label = label
  def __add__(self, other):
    return Molecule([self.label, other.label])
  def __repr__(self):
    return self.label
  
class Molecule:
  def __init__(self, atoms):
    if type(atoms) is list:
	    self.atoms = atoms
  def __repr__(self):
    #a, b = self.atoms               # unpack
    #return f"{a.label}{b.label}"    # return formatted string
    s = ""
    for x in self.atoms:
      s += x.label
    return s
      
sodium = Atom("Na")
chlorine = Atom("Cl")

#salt = Molecule([sodium, chlorine])
salt = sodium + chlorine

print(sodium) # Na
print(salt) # NaCl
print(salt.atoms) # ['Na', 'Cl']
print(salt.atoms[0]) # Na

console log:

Traceback (most recent call last):
  File "script.py", line 28, in <module>
    print(salt) # NaCl
  File "script.py", line 18, in __repr__
    s += x.label
AttributeError: 'str' object has no attribute 'label'

That is a result of setting Molecule to have a list of strings, not objects.

return Molecule([self.label, other.label])

should be,

return Molecule([self, other])

Those two objects have a 'label' attribute.

Not sure how Molecule being returned in Atom is related to Molecule class created below it, how does one not cancel the other out and how do they relate to each other

class Atom:

  def __init__(self, label):

    self.label = label

    

  def __add__(self, other):

    return Molecule([self, other])

   

class Molecule:

  def __init__(self, atoms):

    if type(atoms) is list:

      self.atoms = atoms

sodium = Atom("Na")

chlorine = Atom("Cl")

salt = sodium + chlorine

print (Molecule(salt))

# salt = sodium + chlorine

That line invokes the __add__() method on the Atom instance, which returns a Molecule instance with the two atoms concatenated.

Where did other.label come from? we didn’t define a instance variable like that like we did with self.label = label?

Both self and other are instances of the same class. Look closely.

Isn’t self the instance and other the argument?

def add(self, other):

They are both arguments. other is also a class instance so it has a label attribute, too.

salt = sodium + chlorine

Both sodium and chlorine are Atom instances. The + operator invokes the __add__() method with both operands as arguments.