Python · Syntax · Beginner
OOP: Inheritance and Dunder Methods
OOP problems in Python on inheritance and special methods: child classes, overriding behavior, __str__, __len__, extending base classes, and working with the object model.
Quick topic start and explanations before exercises (exercises below):
Dunder methods — __str__ and __len__
#Inheritance and dunder patterns
#Inheritance and dunder methods quick reference
#Exercises:
Animal and Dog through inheritance.
#Create an Animal class; objects created by it must have a name attribute. The value is passed to the attribute during object initialization. That is, the object describes an animal (any animal) and stores its name. Then create a Dog class that inherits from Animal . In the Dog class add a __str__ method that returns a string containing the type of animal (ahaha, sorry, "animal type" sounds funny) and its name: "Dog: Rex" # example of the returned string The idea is this: The base class Animal stores the main "behavior" of animal objects, and the Dog class specializes it for a specific type of animal.
class Animal:
pass
class Dog(Animal):
pass
Solution
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __str__(self):
return f"Dog: {self.name}"
# You can make it more detailed:
class Dog(Animal):
def __str__(self):
return f"Dog: {self.name}"
def tell(self):
print("Bark!!!") # "Woof" in English. Dogs also need a translator..
class Cat(Animal):
def __str__(self):
return f"Cat: {self.name}"
def tell(self):
print("Cats don't care about human commands!")
# self still needs to be accepted even if it is not used in the method.
Book with string representation.
#Create a Book class with title and pages attributes for objects. Add a __str__ method that returns: "Book: Python Basics (120 pages)" You can also add a __len__ method that returns the number of pages in the book.
class Book:
pass
Solution
class Book:
def __init__(self, title, pages):
self.title = title
self.pages = pages
def __len__(self):
return self.pages
def __str__(self):
return f"Book: {self.title} ({self.pages} pages)"
User and Admin.
#Let's go through something similar to the 1st one again: Task: Create a User class with a name attribute. Create an Admin class that inherits from User . Override the __str__ method so that the output is: "Admin: Alex" And for a regular User, the output through __str__ should simply be: "Alex"
class User:
pass
class Admin(User):
pass
Solution
class User:
def __init__(self, name):
self.name = name
def __str__(self):
return self.name
class Admin(User):
def __str__(self):
return f"Admin: {self.name}"
Box with items.
#Create a Box class with an items_count attribute. Add a __len__ method that returns the number of items. Also add add and remove methods that can put objects into and take objects out of the box. This can be done simply using the items_count counter (just counting without complications), or you can make a list of items in the box (strings) and adapt the class to work with the list (the list is an object attribute).
class Box:
pass
Solution
class Box:
def __init__(self, items_count):
self.items_count = items_count
def add(self, count=1):
self.items_count += count
def remove(self, count=1):
self.items_count -= count
def __len__(self):
return self.items_count
# With a list it is more complicated and different:
class Box:
def __init__(self):
self.items = []
self.items_count = len(self.items)
def add(self, item):
self.items.append(item)
self.items_count = len(self.items)
def remove(self, item):
if item in self.items:
self.items.remove(item)
self.items_count = len(self.items)
else:
print("The cat from the box says: There is no such thing here, goodbye!")
def __len__(self):
return self.items_count
Message with a spam flag.
#Create a Message class with a text attribute and a spam attribute. The message text is passed into text when the object is created, while the spam attribute initially simply has the value None. Add methods: - is_spam - which checks whether the text contains any trigger words as spam criteria and returns True/False - when the is_spam method is called, besides returning True/False, the corresponding value is also stored in the self.spam attribute. - and add __len__ , which returns the length of the text.
class Message:
pass
Solution
class Message:
def __init__(self, text):
self.text = text
self.spam = None
self.spam_triggers = ["куплю", "дорого"] # Yes, I did not talk about this,
# but I did not forbid it either.
def is_spam(self):
if self.spam is not None: # explanation below, I already "overdid" it here
return self.spam
for trigger in self.spam_triggers:
if trigger in self.text:
self.spam = True
return True
self.spam = False
return False
def __len__(self):
return len(self.text)
# Having self.spam after the first call to is_spam will allow repeated checks
# to skip the checking mechanism and simply look at the ALREADY CALCULATED attribute (optimization).
m = Message("Куплю старые телевизоры дорого!!!")
if m.is_spam(): # a real check is performed
...
if m.is_spam(): # it will already be taken from self.spam
...
Employee and Manager.
#Create an Employee class with name and salary attributes. Make a method in Employee that can change the salary. Create a Manager class that inherits from Employee . Override the __str__ method so that the output is: Manager John earns 5000 Make sure that the salary changing method also works for Manager
class Employee:
pass
class Manager(Employee):
pass
Solution
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def change_salary(self, coef): # for example coefficient 1.1 - increase by 10%
self.salary = round(self.salary * coef) # without the second argument it rounds to an integer
class Manager(Employee):
def __str__(self):
return f"Manager {self.name} earns {self.salary}"
chel = Manager("Bob", 1000)
chel.change_salary(1.1)
print(chel) # Manager Bob earns 1100
Playlist and CustomPlaylist.
#Create a Playlist class with a songs attribute (list). Add add and remove methods for adding/removing songs. Add a reverse method that reverses the track list. Add a __len__ method that returns the number of songs. Add a __bool__ method - whether the playlist is empty or not. Then create a child class CustomPlaylist in which the add method allows adding duplicate songs to the playlist. (yes, the first one accordingly should not.)
class Playlist:
pass
Solution
class Playlist:
def __init__(self, songs): # 1 argument - a list of tracks
self.songs = songs
def add(self, song):
if song not in self.songs:
self.songs.append(song)
def remove(self, song):
if song in self.songs:
self.songs.remove(song)
def reverse(self):
self.songs.reverse()
def __len__(self):
return len(self.songs)
def __bool__(self):
return bool(self.songs)
# or
# class Playlist:
# def __init__(self, *songs): # tracks as separate arguments
# self.songs = list(songs) # just decided to remind you about *args
# # the rest is the same.
class CustomPlaylist(Playlist):
def add(self, song):
self.songs.append(song)
Named CustomPlaylist.
#Improve the previous CustomPlaylist class by adding its own __init__, which takes the base behavior of the parent's __init__, but also creates a name for the playlist in the name attribute.
class CustomPlaylist(Playlist):
def __init__???
???
def add(self, song):
self.songs.append(song)
Solution
class CustomPlaylist(Playlist):
def __init__(self, songs, name):
super().__init__(songs)
self.name = name
def add(self, song):
self.songs.append(song)
def __str__(self):
return f"Playlist: {self.name}"
MagicBox with a random item.
#Let's return to the Box class (the code is below), let's inherit from it and create a MagicBox class . The only added behavior is that when a box object is created, a random item from the list will appear inside it. Define the list of possible random items wherever you want, it does not matter, even in the global scope. Template:
from random import choice # function for random choice from a sequence
class Box:
def __init__(self):
self.items = []
self.items_count = len(self.items)
def add(self, item):
self.items.append(item)
self.items_count = len(self.items)
def remove(self, item):
if item in self.items:
self.items.remove(item)
self.items_count = len(self.items)
else:
print("Кот из коробки говорит: Такого тут нет, досвидания!")
def __len__(self):
return self.items_count
things = ["Шапка", "Зонтик", "Кружка"]
class MagicBox(Box):
def __init__(self):
...
Solution
from random import choice # function for random choice from a sequence
class Box:
def __init__(self):
self.items = []
self.items_count = len(self.items)
def add(self, item):
self.items.append(item)
self.items_count = len(self.items)
def remove(self, item):
if item in self.items:
self.items.remove(item)
self.items_count = len(self.items)
else:
print("Кот из коробки говорит: Такого тут нет, досвидания!")
def __len__(self):
return self.items_count
things = ["Шапка", "Зонтик", "Кружка"]
class MagicBox(Box):
def __init__(self):
super().__init__()
self.items.append(choice(things))
self.items_count = len(self.items)
# OR:
class MagicBox(Box):
def __init__(self):
super().__init__()
self.add(choice(things)) # this is cooler
x = MagicBox()
print(x.items)