Programming and IT

Python: OOP

Object-oriented programming is a way of describing a problem through objects that have state and behaviour. In Python its usual four building blocks are simpler than in Java or C++: inheritance exists, private does not, and type compatibility is checked by behaviour, not by ancestry.

Updated
In this article

Inheritance: reuse instead of copying#

A child class gets all the attributes and methods of its parent and can add its own or replace existing ones.

class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary

    def pay(self):
        return self.salary

class Manager(Employee):
    def __init__(self, name, salary, bonus):
        super().__init__(name, salary)
        self.bonus = bonus

    def pay(self):
        return super().pay() + self.bonus

m = Manager("Peter", 100, 50)
print(m.pay(), m.name)          # 150 Peter
print(isinstance(m, Employee))  # True

super() refers to the next class in the inheritance chain. Calling super().__init__(...) in the child's constructor is mandatory if the parent sets anything up: without it the object simply has no such fields, and the crash happens later and somewhere else.

Check types with isinstance, not type(x) == Employee: the latter gives False for a subclass and breaks the whole idea.

Polymorphism and duck typing#

Polymorphism here means: the same call, a different result depending on the object's class.

class Dog:
    def speak(self):
        return "woof"

class Cat:
    def speak(self):
        return "meow"

for animal in (Dog(), Cat()):
    print(animal.speak(), end=" ")   # woof meow

Notice that Dog and Cat are not related by inheritance, and that is no obstacle. Python does not ask which class an object belongs to — it checks whether the object has the method it needs. This is called duck typing: if an object quacks like a duck, the code treats it as a duck. That is why most of the time you do not need a common base class at all.

When you do want to pin down a shared contract, use the abc module:

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self): ...

class Square(Shape):
    def __init__(self, side):
        self.side = side

    def area(self):
        return self.side ** 2

print(Square(4).area())   # 16
Shape()
  # TypeError: Can't instantiate abstract class Shape without an implementation
  # for abstract method 'area'

Encapsulation: underscores instead of private#

The language has no private or protected keywords. There is a convention and one technical trick.

  • name — a public attribute, part of the interface.
  • _name — internal; you should not touch it from outside, but the language does not stop you.
  • __name — the name gets mangled: inside the Account class it becomes _Account__name.
class Account:
    def __init__(self, balance):
        self.__balance = balance

    @property
    def balance(self):
        return self.__balance

a = Account(500)
print(a.balance)           # 500
print(a._Account__balance) # 500 — the "privacy" is bypassed in one line
a.balance = 0
  # AttributeError: property 'balance' of 'Account' object has no setter

The double underscore was not invented to protect against programmers but to keep a name from being accidentally overridden in a subclass. The real way to restrict access is @property without a setter: the attribute can be read but not assigned.

Composition instead of long hierarchies#

Inheritance ties classes together tightly: changing the parent breaks all its descendants. It is often better to make one object a field of another.

class Engine:
    def start(self):
        return "engine started"

class Car:
    def __init__(self):
        self.engine = Engine()      # a car HAS an engine

    def start(self):
        return self.engine.start()

print(Car().start())   # engine started

A simple rule for choosing: "is a" means inheritance (Manager is an Employee), "has a" means composition (Car has an Engine). Multiple inheritance is allowed in Python, and the method lookup order is visible in ClassName.__mro__, but in application code it is almost always worth avoiding.

Practice: write a base class Shape with an abstract area, subclasses Circle and Rect, put them in a list and print the total area with sum(s.area() for s in shapes). How a class itself works is covered in the article on Python classes, and iterating without a list in the article on Python generators.

Step-by-step plan

  1. Parent and childWrite a base class with two fields and a subclass that adds a third via super().__init__.
  2. Override a methodReplace a method in the subclass and call the parent's version through super().
  3. A list of unrelated objectsPut objects of two unrelated classes in a list and call a method with the same name on all of them.
  4. Pin down a contractMake a base class with abc and check that it cannot be instantiated directly.
  5. Rewrite with compositionTake a three-level hierarchy and replace one level with an object stored in a field.

Start learning this in your own space

The plan goes into your repository: tick off stages, keep notes — the change history shows how far you have come.

Start the plan

Check yourself

1.Manager inherits from Employee. What does isinstance(Manager(), Employee) return?

2.Employee.pay() returns 100, and Manager.pay() returns super().pay() + 50. What does Manager().pay() return?

3.Inside the Account class an attribute is declared as self.__balance. Under which name is it reachable from outside?

4.Which relationship is better described by composition than inheritance: "Car has an Engine" or "Manager is an Employee"?

Sources

Was this helpful?

More articles

Programming and IT How to learn to code from scratch You can teach yourself programming without a technical degree if you write code with your own hands from the first week instead of only watching lessons. Below is how to choose a first language, what to install, which order to take topics in and how to tell that you are really making progress. Programming and IT How to learn Python from scratch Python is a good first programming language: code reads almost like text, and the standard library covers most everyday tasks. This plan takes you from installing the interpreter to your own scripts covered by tests in about four months, at roughly an hour a day. Programming and IT Python: slicing A slice cuts a piece out of a sequence by the rule "from which index, up to which, with what step". The notation is compact, but it has two places where almost everyone slips: the right bound is not included, and with a negative step the bounds swap places. Let us go through both. Programming and IT Python: generators A generator hands out values one at a time, and only when someone asks for them. It does not keep the whole sequence in memory, so it suits large files and endless streams. The price is that a generator is single-use: you cannot loop over it twice. Programming and IT Python: regular expressions A regular expression is a pattern that describes a set of strings. In Python you work with them through the `re` module from the standard library. You write the pattern as a string with an `r` prefix, search with `re.search` and pull the result out of a match object. Everything else is details of the pattern language. Programming and IT Python: classes A class describes what data an object is made of and what it can do. The `__init__` constructor fills in a new object, and `self` is a reference to the object itself. Below: a minimal working class, the difference between class and instance attributes, and ways to make the code shorter.

More solutions