Skip to content

Workshop: Classes and OOP Basics (Optional, Self-Study)

View workshop code on GitHub Get the Video Course

From the lesson

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

Six optional workshop problems, one per concept step, in a car scenario. Self-study: check python-workshop/solutions.

Step-01: Why Objects? Your First Class

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

Write a program with one class Car:

  • Task-1: write the class with a class attribute wheels set to 4.
  • Task-2: create two cars c1 and c2, and print each one's wheels.
  • Task-3: print c1 itself, to see a plain object.
Expected output
4
4
<__main__.Car 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 wheels = 4 in the class body (shared by all cars); create c1/c2 with Car() and read c1.wheels. Printing c1 itself shows the default <...Car object at 0x...> because there is no __str__ yet.

Show the solution
s13_workshop01_sol_why_objects_and_first_class.py
# Task-1: the class, with one value shared by every Car
class Car:
    wheels = 4

# Task-2: create two cars and print the shared wheels
c1 = Car()
c2 = Car()
print(c1.wheels)
print(c2.wheels)
# Task-3: print the object itself, which shows a memory address, not data
print(c1)

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 Car:

  • Task-1: write the class with an __init__ that takes a brand and a year.
  • Task-2: create c1 (Mazda, 2020) and print its brand and year on one line.
  • Task-3: create c2 (Tesla, 2023) and print its own brand and year on one line.
Expected output
Mazda 2020
Tesla 2023
Need a hint?

Store both values inside __init__ with self.brand = brand and self.year = year; each Car keeps its OWN attributes, so c2 (Tesla / 2023) stays independent of c1 (Mazda / 2020).

Show the solution
s13_workshop02_sol_init_self_instance_attributes.py
# Task-1: the class, with __init__ storing each value on self
class Car:
    def __init__(self, brand, year):
        self.brand = brand
        self.year = year

# Task-2: create c1 and print its data
c1 = Car("Mazda", 2020)
print(c1.brand, c1.year)

# Task-3: create c2, which keeps its own data
c2 = Car("Tesla", 2023)
print(c2.brand, c2.year)

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: class attribute shared by all, instance attributes per object.

Write a program with one class Car:

  • Task-1: write the class with a class attribute wheels set to 4 and an __init__ taking brand and year.
  • Task-2: create c1 (Mazda, 2020) and c2 (Tesla, 2023), print c1.wheels, then print Car.wheels straight from the class.
  • Task-3: change c1.year to 2021, then print both cars' years.
Expected output
4
4
2021
2023
Need a hint?

wheels = 4 at class level is shared, so c1.wheels and Car.wheels are both 4; brand / year set in __init__ are per-object, so changing c1.year = 2021 leaves c2 untouched.

Show the solution
s13_workshop03_sol_class_vs_instance_attributes.py
# Task-1: the class, with a shared class attribute and per-object data
class Car:
    wheels = 4

    def __init__(self, brand, year):
        self.brand = brand
        self.year = year

# Task-2: both cars, then the class attribute read from the object and from the class
c1 = Car("Mazda", 2020)
c2 = Car("Tesla", 2023)
print(c1.wheels)
print(Car.wheels)

# Task-3: an instance attribute changes for c1 only
c1.year = 2021
print(c1.year)
print(c2.year)

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, returning vs printing.

Write a program with one class Car:

  • Task-1: write the class with an __init__ that takes a brand and a speed, a method accelerate that adds an amount to the speed, and a method status that RETURNS <brand> at <speed> km/h.
  • Task-2: create c1 (Mazda, 40), accelerate it by 20, and print c1.status().
Expected output
Mazda at 60 km/h
Need a hint?

accelerate uses self to change state (self.speed = self.speed + amount, it ACTS), while status RETURNS f"{self.brand} at {self.speed} km/h". After c1.accelerate(20) the speed becomes 60, so c1.status() returns the line Mazda at 60 km/h.

Show the solution
s13_workshop04_sol_methods.py
# Task-1: the class, with accelerate() that ACTS and status() that RETURNS
class Car:
    def __init__(self, brand, speed):
        self.brand = brand
        self.speed = speed

    def accelerate(self, amount):
        self.speed += amount

    def status(self):
        return f"{self.brand} at {self.speed} km/h"

# Task-2: create the car, accelerate it, then print what status() RETURNS
c1 = Car("Mazda", 40)
c1.accelerate(20)
print(c1.status())

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__ friendly, __repr__ exact.

Write a program with one class Car:

  • Task-1: write the class with an __init__ that takes a brand and a year, __str__ returning <brand> (<year>), and __repr__ returning Car(brand='<brand>', year=<year>).
  • Task-2: create c1 (Mazda, 2020) and c2 (Tesla, 2023), and print c1.
  • Task-3: print a list holding both cars.
Expected output
Mazda (2020)
[Car(brand='Mazda', year=2020), Car(brand='Tesla', year=2023)]
Need a hint?

__str__ returns the friendly f"{self.brand} ({self.year})" that print(c1) shows; __repr__ returns the exact Car(brand='...', year=...) form (an f-string) that a list [c1, c2] shows. Match the dunder names exactly.

Show the solution
s13_workshop05_sol_str_and_repr.py
# Task-1: the class, with both dunder methods
class Car:
    def __init__(self, brand, year):
        self.brand = brand
        self.year = year

    def __str__(self):
        return f"{self.brand} ({self.year})"

    def __repr__(self):
        return f"Car(brand={self.brand!r}, year={self.year!r})"

# Task-2: create both cars; print() uses __str__
c1 = Car("Mazda", 2020)
c2 = Car("Tesla", 2023)
print(c1)
# Task-3: a list of objects uses __repr__
print([c1, c2])

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 asking the helper about an object the class method just built.

Write a program with one class Car:

  • Task-1: write the class with an __init__ that takes a brand and a year, a @classmethod from_dict(cls, data) that builds a Car from a dict with keys brand and year, and a @staticmethod is_vintage(year) that returns True when year is below 1990.
  • Task-2: print Car.is_vintage(1985).
  • Task-3: build a car from {"brand": "Mazda", "year": 2020} using from_dict, and print its brand and year.
  • Task-4: print whether that car is vintage, by passing its year to is_vintage.
Expected output
True
Mazda 2020
False
Need a hint?

@staticmethod is_vintage(year) takes no self / cls, so it can simply return year < 1990. @classmethod from_dict(cls, data) receives the class as cls, so return cls(data["brand"], data["year"]) builds a Car; read it with .brand and .year, then pass that same .year back to Car.is_vintage(...) for the last line.

Show the solution
s13_workshop06_sol_classmethod_staticmethod.py
# Task-1: the class, with the two decorated methods
class Car:
    def __init__(self, brand, year):
        self.brand = brand
        self.year = year

    @classmethod
    def from_dict(cls, data):
        return cls(data["brand"], data["year"])

    @staticmethod
    def is_vintage(year):
        return year < 1990

# Task-2: a static helper, no object needed
print(Car.is_vintage(1985))

# Task-3: an alternate constructor that builds a Car from a dict
c = Car.from_dict({"brand": "Mazda", "year": 2020})
print(c.brand, c.year)

# Task-4: the same static helper, now asked about the car we just built
print(Car.is_vintage(c.year))

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 practice 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

Workshop: Standard Library Essentials

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