Search code examples
pythonname-mangling

Subclass not inheriting structure from superclass properly


I'm just starting out with Python, but I cannot figure out why I'm having a problem with such simple class inheritance, and despite the common use of the tutorial I've been following, I haven't seen anyone else on Stack Overflow encountering this issue. Here's the code (don't worry, nothing too complicated):

import random
import sys
import os

class Animal:
    __name = ""
    __height = 0
    __weight = 0
    __sound = 0

    def __init__(self, name, height, weight, sound):
        self.__name = name
        self.__height = height
        self.__weight = weight
        self.__sound = sound

    def toString(self):
        return "{} is {} cm tall and {} kilograms and says {}".format(self.__name,
                                                                      self.__height,
                                                                      self.__weight,
                                                                      self.__sound)

cat = Animal ('Whiskers', 33, 10, 'meow')
print(cat.toString())

bird = Animal ('Flutie', 33, 10, 'tweet')
print(bird.toString())

class Dog(Animal):

    def __init__(self, name, height, weight, sound):
        super(Dog, self).__init__(name, height, weight, sound)

    def toString(self):
        return "{} is {} cm tall and {} kilograms and says {}".format(self.__name,
                                                                      self.__height,
                                                                      self.__weight,
                                                                      self.__sound)

spot = Dog ('Spot', 53, 27, "Woof")

print(spot.toString())

...And here's the output:

Whiskers is 33 cm tall and 10 kilograms and says meow
Flutie is 33 cm tall and 10 kilograms and says tweet
Traceback (most recent call last):
  File "C:/.../animal_test.py", line 72, in <module>
    print(spot.toString())
  File "C:/.../animal_test.py", line 65, in toString
    return "{} is {} cm tall and {} kilograms and says {}".format(self.__name,
AttributeError: 'Dog' object has no attribute '_Dog__name'

Solution

  • The double underscores represents name mangling.

    class Animal:
        def __init__(self, name, height, weight, sound):
            self.__name = name
            self.__height = height
            self.__weight = weight
            self.__sound = sound
    

    Literally translates to this when being interpreted:

    class Animal:
        def __init__(self, name, height, weight, sound):
            self._Animal__name = name
            self._Animal__height = height
            self._Animal__weight = weight
            self._Animal__sound = sound
    

    It doesn’t matter where it’s called, or who called the __init__, the prefix _Animal will take place because it’s physically located under the Animal class.

    But when you used the attributes here, as it’s physically located under the Dog class, the got name mangled to this:

    class Dog(Animal):
    
        def __init__(self, name, height, weight, sound):
            super(Dog, self).__init__(name, height, weight, sound)
    
        def toString(self):
            return "{} is {} cm tall and {} kilograms and says {}".format(self._Dog__name,
                                                                          self._Dog__height,
                                                                          self._Dog__weight,
                                                                          self._Dog__sound)
    

    Which the Dog object definitely doesn’t have an attribute named self._Dog__name, instead it has the attribute self._Animal__name.