Skip to content

Practice: Classes and OOP Basics

View practice code on GitHub Get the Video Course

From the lesson

Practice problems for Section 13 Concepts. Build each yourself first, then expand the solution to check.

Six practice problems, one per concept step; the numbers line up. Build each yourself, then check python-practice/solutions.

These problems use a music app theme (a Song class with a shared app name, plays, and readable songs), a different domain from the Pyzon Store you build in the concept files, so you apply each idea instead of copying the demo.

Step-01: Why Objects? Your First Class

Concepts: a class as a blueprint, a class attribute, creating objects.

Write a program with one class Song:

  • Task-1: write the class with a single class attribute app set to Tunefy.
  • Task-2: create two songs s1 and s2 from it, and print each one's app.
  • Task-3: print s1 itself, to see what a plain object looks like.
Expected output
Tunefy
Tunefy
<__main__.Song object at 0x...>

The number after 0x is a memory address, so it is different on every run. Yours will not match this one.

Need a hint?

Put app = "Tunefy" inside the class body (not in a method) so every Song shares it; create objects with Song() and read the shared value as s1.app. Printing s1 itself shows the default <...Song object at 0x...> because there is no __str__ yet.

Show the solution
s13_practice01_sol_why_objects_and_first_class.py
# Task-1: the class, with one value shared by every Song
class Song:
    app = "Tunefy"

# Task-2: create two songs and print the shared app
s1 = Song()
s2 = Song()
print(s1.app)
print(s2.app)

# Task-3: print the object itself, which shows a memory address, not data
print(s1)

View the full solution file on GitHub

Pages for this step: Concept | Practice | Workshop

Python Files for this step: Concept | Practice | Practice Solutions | Workshop | Workshop Solutions

Step-02: Give Each Object Its Own Data (__init__ and self)

Concepts: __init__, self, instance attributes.

Write a program with one class Song:

  • Task-1: write the class with an __init__ that takes a title and an artist and stores each on self.
  • Task-2: create s1 with title Yesterday and artist The Beatles, and print its title and artist on one line.
  • Task-3: create s2 with title Hello and artist Adele, and print its own title and artist on one line.
Expected output
Yesterday The Beatles
Hello Adele
Need a hint?

Store both values inside __init__ with self.title = title and self.artist = artist; each Song keeps its OWN attributes, so s2 (Hello / Adele) stays independent of s1 (Yesterday / The Beatles).

Show the solution
s13_practice02_sol_init_self_instance_attributes.py
# Task-1: the class, with __init__ storing each value on self
class Song:
    def __init__(self, title, artist):
        self.title = title
        self.artist = artist

# Task-2: create s1 and print its data
s1 = Song("Yesterday", "The Beatles")
print(s1.title, s1.artist)

# Task-3: create s2, which keeps its own data
s2 = Song("Hello", "Adele")
print(s2.title, s2.artist)

View the full solution file on GitHub

Pages for this step: Concept | Practice | Workshop

Python Files for this step: Concept | Practice | Practice Solutions | Workshop | Workshop Solutions

Step-03: Class Attributes vs Instance Attributes

Concepts: a class attribute shared by all, instance attributes per object, precedence.

Write a program with one class Song:

  • Task-1: write the class with a class attribute app set to Tunefy and an __init__ taking a title and an artist.
  • Task-2: create s1 (Yesterday, The Beatles) and s2 (Hello, Adele), print s1.app, then print Song.app straight from the class.
  • Task-3: give s2 its own app value MyTunes, then print s2.app, s1.app and Song.app, so the instance attribute wins for s2 while the others stay Tunefy.
Expected output
Tunefy
Tunefy
MyTunes
Tunefy
Tunefy
Need a hint?

Set app = "Tunefy" at class level (shared by all Songs) and take title / artist in __init__ (one per object). Assigning s2.app = "MyTunes" makes an INSTANCE attribute that shadows the class one for s2 only, so s1.app and Song.app stay Tunefy.

Show the solution
s13_practice03_sol_class_vs_instance_attributes.py
# Task-1: the class, with a shared class attribute and per-object data
class Song:
    app = "Tunefy"

    def __init__(self, title, artist):
        self.title = title
        self.artist = artist

# Task-2: both songs, then the class attribute read from the object and from the class
s1 = Song("Yesterday", "The Beatles")
s2 = Song("Hello", "Adele")
print(s1.app)
print(Song.app)

# Task-3: an instance attribute on s2 wins for s2 only
s2.app = "MyTunes"
print(s2.app)
print(s1.app)
print(Song.app)

View the full solution file on GitHub

Pages for this step: Concept | Practice | Workshop

Python Files for this step: Concept | Practice | Practice Solutions | Workshop | Workshop Solutions

Step-04: Adding Behavior with Methods

Concepts: a method uses self, a method changes data, returning vs acting.

Write a program with one class Song:

  • Task-1: write the class with an __init__ that takes a title and an artist and sets self.plays to 0, a method play that adds 1 to self.plays, and a method label that RETURNS the text <title> by <artist>.
  • Task-2: create s1 for Yesterday by The Beatles, play it 3 times, and print s1.plays.
  • Task-3: print s1.label().
Expected output
3
Yesterday by The Beatles
Need a hint?

A method takes self first: play ACTS (self.plays = self.plays + 1, no return), while label RETURNS an f-string f"{self.title} by {self.artist}". Call s1.play() three times, then print(s1.plays) and print(s1.label()).

Show the solution
s13_practice04_sol_methods.py
# Task-1: the class, with play() that ACTS and label() that RETURNS
class Song:
    def __init__(self, title, artist):
        self.title = title
        self.artist = artist
        self.plays = 0

    def play(self):
        self.plays += 1

    def label(self):
        return f"{self.title} by {self.artist}"

# Task-2: create the song, play it three times, then print the changed counter
s1 = Song("Yesterday", "The Beatles")
s1.play()
s1.play()
s1.play()
print(s1.plays)

# Task-3: print what label() RETURNS
print(s1.label())

View the full solution file on GitHub

Pages for this step: Concept | Practice | Workshop

Python Files for this step: Concept | Practice | Practice Solutions | Workshop | Workshop Solutions

Step-05: Readable Objects: __str__ and __repr__

Concepts: __str__ for a friendly printout, __repr__ for the exact one; both are dunder methods, so the name must match exactly.

Write a program with one class Song:

  • Task-1: write the class with an __init__ that takes a title and an artist, __str__ returning <title> by <artist>, and __repr__ returning Song(title='<title>', artist='<artist>').
  • Task-2: create s1 (Yesterday, The Beatles) and s2 (Hello, Adele), and print s1.
  • Task-3: print a list holding both songs.
Expected output
Yesterday by The Beatles
[Song(title='Yesterday', artist='The Beatles'), Song(title='Hello', artist='Adele')]
Need a hint?

__str__ returns the friendly f"{self.title} by {self.artist}" that print(s1) shows; __repr__ returns the exact Song(title='...', artist='...') form (build it with an f-string) that a list [s1, s2] shows for each item. Both dunder names need the double underscores on each side.

Show the solution
s13_practice05_sol_str_and_repr.py
# Task-1: the class, with both dunder methods
class Song:
    def __init__(self, title, artist):
        self.title = title
        self.artist = artist

    def __str__(self):
        return f"{self.title} by {self.artist}"

    def __repr__(self):
        return f"Song(title={self.title!r}, artist={self.artist!r})"

# Task-2: create both songs; print() uses __str__
s1 = Song("Yesterday", "The Beatles")
s2 = Song("Hello", "Adele")
print(s1)

# Task-3: a list of objects uses __repr__
print([s1, s2])

View the full solution file on GitHub

Pages for this step: Concept | Practice | Workshop

Python Files for this step: Concept | Practice | Practice Solutions | Workshop | Workshop Solutions

Step-06: Class Methods and Static Methods (@classmethod and @staticmethod)

Concepts: the decorators @classmethod (an alternate constructor: it receives the class as cls) and @staticmethod (a related helper: it takes neither self nor cls), and using both together.

Write a program with one class Song:

  • Task-1: write the class with an __init__ that takes a title, an artist and a duration in seconds, a @classmethod from_dict(cls, data) that builds a Song from a dict with keys title, artist and duration, and a @staticmethod is_valid_duration(duration) that returns True when duration is above 0.
  • Task-2: print Song.is_valid_duration(125).
  • Task-3: take the two dicts {"title": "Yesterday", "artist": "The Beatles", "duration": 125} and {"title": "Silence", "artist": "Unknown", "duration": 0}. For each one, build the song with from_dict and print its title and duration only when is_valid_duration says the duration is valid, and print Duration is not valid when it is not.
Expected output
True
Yesterday 125
Duration is not valid
Need a hint?

@staticmethod is_valid_duration(duration) takes no self / cls, so it can simply return duration > 0, and it is called through the class as Song.is_valid_duration(125). @classmethod from_dict(cls, data) receives the class as cls, so return cls(data["title"], data["artist"], data["duration"]) builds a Song. Loop over the two dicts; inside the loop call the helper on data["duration"] in an if, build and print (.title and .duration) when it says True, and print the Duration is not valid line in the else.

Show the solution
s13_practice06_sol_classmethod_staticmethod.py
# Task-1: the class, with the two decorated methods
class Song:
    def __init__(self, title, artist, duration):
        self.title = title
        self.artist = artist
        self.duration = duration

    @classmethod
    def from_dict(cls, data):
        return cls(data["title"], data["artist"], data["duration"])

    @staticmethod
    def is_valid_duration(duration):
        return duration > 0

# Task-2: a static helper, no object needed
print(Song.is_valid_duration(125))

# Task-3: the helper checks first, then the alternate constructor builds the Song
songs = [{"title": "Yesterday", "artist": "The Beatles", "duration": 125},
         {"title": "Silence", "artist": "Unknown", "duration": 0}]
for data in songs:
    if Song.is_valid_duration(data["duration"]):
        s1 = Song.from_dict(data)
        print(s1.title, s1.duration)
    else:
        print("Duration is not valid")

View the full solution file on GitHub

Pages for this step: Concept | Practice | Workshop

Python Files for this step: Concept | Practice | Practice Solutions | Workshop | Workshop Solutions

Finished this section?

Check your work against the solutions above, then head back to the Section 13 lesson, or try the Section 13 workshop for more reps.

Prefer to learn by watching?

The full video course teaches every concept on screen and builds the projects with you, step by step.

Get the Video Course

Practice: Virtual Environments and Packaging

🎉 New Course
Python for Absolute Beginners
$15.99 $84.99 81% OFF
Get the Video Course