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.TestCasetest e.g., test_is_same_person()assertTrue(actual) : test passes if actual == TrueassertFalse(actual) : test passes if actual == FalseassertEquals(actual, expected) : test passes if actual == expectedwith 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: