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

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.

Sorry I I guess I’m having a hard time understanding.
for instance variables don’t we set it as instance.attribute=argument? With other.label it would be argument.attribute=argument? Which wouldn’t make sense. For me both of sodium & chlorine would be self.label & self.label? Sodium.label=“Na” & Chlorine.label=“Cl” Maybe I’m making it sound confusing…

self is one instance, other is the other. They can’t both have the same owner name.

When we invoke the method on sodium it looks for the other operand, chlorine.