Inheritance

A class can inherit attributes and methods from another class.

class Animal:
    def __init__(self, name):
        self.name = name
 
    def speak(self):
        pass
 
class Dog(Animal):
    def speak(self):
        return "Woof"
 
class Cat(Animal):
    def speak(self):
        return "Meow"
dog = Dog("Rex")
cat = Cat("Michi")
 
dog.speak()   # "Woof"
cat.speak()   # "Meow"

Note

Dog and Cat inherit from Animal. They override the speak method to provide their own implementation.

super()

super() calls methods from the parent class:

class Animal:
    def __init__(self, name):
        self.name = name
 
class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)   # call parent __init__
        self.breed = breed
dog = Dog("Rex", "German Shepherd")
dog.name    # "Rex"
dog.breed   # "German Shepherd"

Calling parent methods

class Animal:
    def speak(self):
        print("Generic sound")
 
class Dog(Animal):
    def speak(self):
        super().speak()         # print "Generic sound" first
        print("Woof")           # then add own behavior

Polymorphism

Different classes can implement the same method with different behavior:

class Animal:
    def speak(self):
        pass
 
class Dog(Animal):
    def speak(self):
        return "Woof"
 
class Cat(Animal):
    def speak(self):
        return "Meow"
 
def make_speak(animal):
    print(animal.speak())
 
make_speak(Dog("Rex"))    # "Woof"
make_speak(Cat("Michi"))  # "Meow"

Tip

Polymorphism works because Python uses duck typing: if an object has the method, it can be used, regardless of its class.

Properties

Properties allow controlled access to attributes using @property:

class Person:
    def __init__(self, name, age):
        self.__name = name    # private attribute (name mangling)
        self.age = age
 
    @property
    def name(self):
        return self.__name
 
    @name.setter
    def name(self, value):
        if not value:
            raise ValueError("Name cannot be empty")
        self.__name = value
p = Person("Ana", 25)
p.name            # "Ana" (uses getter)
p.name = "Luis"   # uses setter
p.name = ""       # ValueError: Name cannot be empty

Note

__name uses name mangling: Python internally renames it to _Person__name to avoid name conflicts in subclasses. It is not truly private, but signals it should not be accessed directly.

Types of attributes

TypeSyntaxDescription
Publicself.attrAccessible from anywhere
Protectedself._attrConvention: internal use (not enforced)
Privateself.__attrName mangling: _ClassName__attr
class Example:
    def __init__(self):
        self.public = 1
        self._protected = 2
        self.__private = 3
 
obj = Example()
obj.public          # 1
obj._protected      # 2 (accessible but not recommended)
# obj.__private     # AttributeError
obj._Example__private   # 3 (access via name mangling)

Next: Modules and Packages