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:
The
__init__
method is the constructor function which is called when a new class object is instantiated.The
start
method is a user-defined function.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