A glimpse into classes#

Let’s have a brief look how classes work in Python.

Classes in Python#

In Python, everything is an object, every object has a class.

i = 1
print(i.__class__)
s = "hello"
print(s.__class__)
<class 'int'>
<class 'str'>

A class collects attributes and functions together. Here is an example of a class definition for a car:

class Car:
    """A class representing a car"""

    def __init__(self, year, fuel="electric"):
        self.year = year
        self.fuel = fuel
        self.speed = 0

    def start(self):
        if self.fuel == "electric":
            print("sssss")
        else:
            print("Wohmmm")

    def accelerate(self, new_speed):
        self.speed = new_speed

This class contains three methods:

  1. The __init__ method is the constructor function which is called when a new class object is instantiated.

  2. The start method is a user-defined function.

  3. The accelerate method is also a user-defined function.

Note: All instance methods take a self variable as first argument. This variable represents the car object itself. We can use the self variable to access information about the object.

Using our Python class#

We can create a new instance of the class:

mycar = Car(year=2007, fuel="Bensin")
print(mycar)
<__main__.Car object at 0x7f0b4c2041c0>

Instantiating the class calls the __init__ constructor and returns a new object, an instance of our class:

We can call the user-defined methods on our object:

mycar.start()
mycar.accelerate(new_speed=100)
Wohmmm

Note: When calling instance methods, the self argument in the declaration is not included.

We can also access the member attributes of the object:

print(mycar.year)
print(mycar.fuel)
print(mycar.speed)
2007
Bensin
100

Magic class methods#

  • Python classes can implement some magic methods.

  • If implemented, Python calls these magic methods on certain operations.

  • Here are some operations defined by magic methods:

str(a)            # calls a.__str__(), called also with print(a)!
len(a)            # calls a.__len__()
c = a*b           # calls c = a.__mul__(b)
a = a+b           # calls a = a.__add__(b)
a += c            # calls a.__iadd__(c)
d = a[3]          # calls d = a.__getitem__(3)
a[3] = 0          # calls a.__setitem__(3, 0)
f = a(1.2, True)  # calls f = a.__call__(1.2, True)
if a:             # calls if a.__len__()> 0: or if a.__nonzero__():
a == b            # calls a.__eq__(self, b)

A complete list is available here: Python reference - Data model

Example#

Currently, when we try to print our Car object the output is not very informative:

print(mycar)
<__main__.Car object at 0x7f0b4c2041c0>

Let’s extend our car class with the magic __str__ method:

class Car:
    """A class representing a car"""

    def __init__(self, year, fuel="electric"):
        self.year = year
        self.fuel = fuel
        self.speed = 0

    def __str__(self):
        return f"This is a car from {self.year} driving on {self.fuel} at speed {self.speed}"

This magic __str__ method is now called when we convert the car object to a string:

mycar = Car(2007, "bensin")
str(mycar)
'This is a car from 2007 driving on bensin at speed 0'

Subclasses#

Remember: A class collects attributes and functions that belong together.

Subclasses can be used to specialize and extend existing classes.

Technical notes on subclasses:

  • Python supports single and multiple inheritance

  • Python has a similar class concept as in Java and C++, but:

    • All functions are virtual

    • No private/protected variables (the effect can be “simulated”)

    • This makes class programming easier and faster than in C++ and Java:

class Car:
    """A class representing a car"""

    def __init__(self, color):
        self.color = color
        self.sound = "Brooom"

    def start(self):
        print(self.sound)

Implementing a subclass#

Subclasses can be used to inherit the member functions/attributes from its Base class.

It is useful to create specialisation of a class without code duplication.

Let’s create a subclass ElectricCar that inherits the properties from the Car class:

class ElectricCar(Car):
    def __init__(self, color):
        super().__init__(color)  # Calls the parent constructor
        # self.color and self.sound are now class members
        self.sound = "Sssss"  # Change the sound

    def stop(self):  # Add a new method to ElectricCars
        print("Engine stopped")

If a subclass method is not implemented, Python will try to call the function of the parent class:

ecar = ElectricCar("white")
ecar.start()
ecar.stop()

Extending default types#

Everything is an object! Everything has a class!

Let’s try counting occurrences of strings in a sequence and storing them in a dict:

counts = dict()
for word in ["a", "b", "c", "a", "b"]:
    if word not in counts:
        # we have to check every time if we've seen this before!
        counts[word] = 0
    counts[word] += 1
counts
{'a': 2, 'b': 2, 'c': 1}

We can define a custom subclass of dict that overrides __getitem__ so that it returns a default value when a key hasn’t been seen before

class DefaultDict(dict):
    """A dictionary that returns a value for undefined keys"""

    def __init__(self, default_item):
        self.default_item = default_item

    def __getitem__(self, key):
        if key not in self:
            self[key] = self.default_item
        return super().__getitem__(key)
counts = DefaultDict(default_item=0)
for word in ["a", "b", "c", "a", "b"]:
    counts[word] += 1
counts
{'a': 2, 'b': 2, 'c': 1}

counts’ class is our DefaultDict, but because it’s a subclass, isinstance returns True for any parent class of the checked object:

print(counts.__class__)
print(isinstance(counts, dict))
<class '__main__.DefaultDict'>
True

Unit tests#

Unit testing is a software testing method by which individual units of source code are tested determine if they are fit to use. Unit testing your code will help you

  • avoid bugs

  • avoid breaking old code when you make changes

  • provide documentation in the form of a minimal working example

Example: Let’s say we have written a function add which adds two integers.

def test_two_plus_two():
    # Test that 2 + 2 = 4
    assert add(2,2) == 4, 'addition not returning correct answer'

Features:

  • Function name should start or end with the word ‘test’

  • Function should not take any arguments

  • Test function should not be randomized in any way

Unit tests can be systematically run with e.g. pytest. For more information see the Pytest home-page