Private attributes and derived classes
In the module on encapsulation you made attributes private by starting their names with two underscores. A private attribute can only be used in the class's own methods. The class's clients, the code that uses its objects, can't reach it, which keeps the object's data safe from careless changes.
Inheritance raises a new question: is a derived class like the class's own methods, or like any other client? For private attributes, it's like any other client. A derived class can't use its base class's private attributes either:
class Playlist:
def __init__(self, name):
self.name = name
self.__songs = []
def add(self, title, seconds):
self.__songs.append((title, seconds))
def count(self):
return len(self.__songs)
class TimedPlaylist(Playlist):
def duration(self):
total = 0
for title, seconds in self.__songs:
total += seconds
return f"{total // 60} min {total % 60} s"
mix = TimedPlaylist("Road trip")
mix.add("Highway Song", 215)
mix.add("Slow River", 187)
print(mix.count())
print(mix.duration())
2Traceback (most recent call last):
File "main.py", line 25, in <module>
print(mix.duration())
~~~~~~~~~~~~^^
File "main.py", line 16, in duration
for title, seconds in self.__songs:
^^^^^^^^^^^^
AttributeError: 'TimedPlaylist' object has no attribute '_TimedPlaylist__songs'. Did you mean: '_Playlist__songs'?The playlist keeps each song as a pair of a title and a length in seconds. add and count work, because they are Playlist's own methods. duration, which adds up the lengths, fails.
What the error message tells you
The message shows how Python hides a private attribute: it quietly renames it, putting the class's name in front. This renaming is called name mangling. Inside the class Playlist, self.__songs really means self._Playlist__songs. Inside TimedPlaylist, the same words self.__songs mean self._TimedPlaylist__songs, and the playlist has no attribute of that name.
Python's "Did you mean" points at the real name, _Playlist__songs. Writing that would work, but don't: it goes around the lock on purpose, and the code breaks as soon as someone renames the class. The renaming has a good side, too. A derived class can have a private attribute of its own with the same name as one in its base class, and the two never get mixed up.
Go through the public methods
A derived class can use everything public in its base class, just as any client can. So if the base class offers a method or a property that gives the data, the derived class uses that:
class Playlist:
def __init__(self, name):
self.name = name
self.__songs = []
def add(self, title, seconds):
self.__songs.append((title, seconds))
def songs(self):
# A copy, so the private list stays safe.
return self.__songs[:]
class TimedPlaylist(Playlist):
def duration(self):
total = 0
for title, seconds in self.songs():
total += seconds
return f"{total // 60} min {total % 60} s"
mix = TimedPlaylist("Road trip")
mix.add("Highway Song", 215)
mix.add("Slow River", 187)
mix.add("River Bend", 242)
print(mix.duration())
10 min 44 sThis is often the best way: the base class keeps control of its data, and the derived class only asks for it. When a base class has no such method and its derived classes really need the data, the base class can share it in another way, which is the topic of the next step.