Guidance for the item(s) below:
Similar to last week, we strongly recommend you to watch all pre-recorded lecture videos allocated to this week. Furthermore, Learn this week's OOP topic (W10.1) before starting the topic below, as the two are highly interlinked. The same lecture video is given below for your convenience.
You can make a class from another class. If you do, it is as if the already has all the attributes and methods of the .
Syntax:
class ChildClassName(ParentClassName):
# statements of the class
Consider the Person
class below:
| → |
|
The Teacher
class below inherits from the Person
class given above.
| → |
|
Observe how,
Teacher
object can use the print_info()
method defined in the parent class.dan = Teacher('Dan')
invokes the __init__()
method defined in the parent class too.print('the name is', dan.name)
is accessing the attribute name
from a Teacher
object although the attribute is defined in the parent class.teach
accesses the attribute name
using self.name
although the attribute is defined in the parent class.A child class can override a method defined in the parent class. That way, a child object can change a behavior defined in the parent class.
Note how the Student
class below overrides the print_info()
method of the parent class Person
.
| → |
|
When overriding methods, you can reuse the parent's definition of the same method using the super().
prefix.
class Person:
def __init__(self, name):
self.name = name
Given that Person
class has the initializer method given above, the following two versions of the Student
class are equivalent.
(a) Override without reusing parent's method
class Student(Person):
def __init__(self, name, matric):
self.name = name
self.matric = matric
(b) Override but reuse parent's method (preferred)
class Student(Person):
def __init__(self, name, matric):
super().__init__(name) # reuse parent's method
self.matric = matric
Note that all python classes automatically inherits from the built-in class object
even if you did not specify it as the parent class. The object
class has a __str__()
method that you can ovrride in your classes to customize how the print
function will print an object of your class.
The Book
class below overrides the __str__()
method so that Book
objects can be printed in a specific format.
| → |
|
A class can inherit from multiple classes. If multiple parent classes have the same method, the one that is given first (in the order of inheritance) will be used.
|
The TeachingAssistant
class above inherits from both Student
class and the Teacher
class both of which inherit from the Person
class. That means a TeachingAssistant
object can use methods from classes object
, Person
, Student
, Teacher
, and TeachingAssistant
.
| → |
|
As both Teacher
and Student
classes have the print_info()
method, the method from the Teacher
class will be used as it comes first in the inheritance order (Teacher, Student)
; that is why you see Elsie is a teacher
in the output instead of Elsie is a student
.
You need to use inheritance when you create user-defined exceptions because all such exceptions need to inherit from a built-in Exception
class.
In the example below, EmptyCommandError
and InvalidCommandError
are user-defined exceptions. The latter has overridden the constructor to take additional parameters.
class EmptyCommandError(Exception):
"""Indicates a task has expired."""
pass
class InvalidCommandError(Exception):
"""Indicates that the user entered an invalid command"""
def __init__(self, command, explanation):
self.command = command
self.explanation = explanation
def execute_command(command):
if command == '':
raise EmptyCommandError()
elif len(command) < 4:
raise InvalidCommandError(command, "command too short")
def process(command):
try:
execute_command(command)
except EmptyCommandError:
print('empty command')
except InvalidCommandError as e:
print('invalid command:', e.command, '->', e.explanation)
process('')
process('HA')
empty command
invalid command: HA -> command too short
Guidance for the item(s) below:
Let us learn how to automate the testing of Python code using unit tests (one of the testing types you learned under this week's SE topics).
The built-in module unittest
supports automation of unit testing in an object-oriented way.
Let's assume you have a file called search.py
which has the following two functions.
def get_first_name(name):
"""Return the first part of the parameter 'name'"""
return name.split()[0]
def is_same_person(person, keyword):
"""Return True if the parameter 'person' (type: dictionary)
contains a key 'name' whose value contains the
parameter 'keyword' (type: string)
e.g.,
* is_same_person({'name': 'jackie'}, 'jack') returns True
* is_same_person({'name': 'jackie'}, 'jackie-chan') returns False
"""
return keyword in person['name']
This is how we can write some unit tests for the two functions.
import search, unittest
class TestSearch(unittest.TestCase):
def test_is_same_person(self):
jack = {'name':'jack'}
self.assertTrue(search.is_same_person(jack, 'jack'))
self.assertTrue(search.is_same_person(jack, 'ack'))
self.assertTrue(search.is_same_person(jack, 'ac'))
self.assertTrue(search.is_same_person(jack, 'j'))
self.assertTrue(search.is_same_person(jack, 'k'))
self.assertFalse(search.is_same_person(jack, 'jackie'))
self.assertFalse(search.is_same_person(jack, 'blackjack'))
self.assertFalse(search.is_same_person({'name': 'x', 'other': 'jack'}, 'jack'))
with self.assertRaises(KeyError):
search.is_same_person({}, 'jack')
def test_get_first_name(self):
self.assertEqual(search.get_first_name('Amy'), 'Amy')
self.assertEqual(search.get_first_name('Amy Bernice'), 'Amy')
self.assertEqual(search.get_first_name('Amy-Bernice'), 'Amy-Bernice')
with self.assertRaises(IndexError):
search.get_first_name('')
# activate the test runner
if __name__ == '__main__':
unittest.main()
When you run the above code, each method in the test class will be executed by a built-in test runner and the result will be reported. An example is given below:
...
----------------------------------------------------------------------
Ran 2 tests in 0.019s
OK
Things to note:
unittest.TestCase
test
e.g., test_is_same_person()
assertTrue(actual)
: test passes if actual == True
assertFalse(actual)
: test passes if actual == False
assertEquals(actual, expected)
: test passes if actual == expected
with self.assertRaises(Exception):
passes if the code block it contains raises the specified exception.If the expected value is not as same as the actual, the test runner will report the test failure. For example, if we were to insert this statement into test_get_first_name
method,
self.assertEqual(search.get_first_name('Amy Foo'), 'Foo')
the output will be something like this:
F.
======================================================================
FAIL: test_get_first_name (__main__.TestSearch)
----------------------------------------------------------------------
Traceback (most recent call last):
File "main.py", line 19, in test_get_first_name
self.assertEqual(search.get_first_name('Amy Foo'), 'Foo')
AssertionError: 'Amy' != 'Foo'
- Amy
+ Foo
----------------------------------------------------------------------
Ran 2 tests in 0.001s
FAILED (failures=1)
📎 Resources: