26. Methods and self — Homework solutions

The .py solution files are in exercises/26/homework/solutions/.

Problem 1 — Counter object

Problem. A Counter class with inc, get, and reset methods.

How to think about it. The object holds the state in self.count; each method reads or changes it. Python passes the object as self automatically when you call the method on an instance.

Worked solution.

class Counter:
    def __init__(self):
        self.count = 0

    def inc(self):
        self.count = self.count + 1

    def get(self):
        return self.count

    def reset(self):
        self.count = 0

counter = Counter()
counter.inc()
counter.inc()
counter.inc()
print(counter.get())   # 3

counter.reset()
print(counter.get())   # 0

Common mistakes.

  • Forgetting self as the first parameter of each method. Python will raise a TypeError saying the method takes 0 arguments but 1 was given.
  • Writing count = 0 at class level instead of self.count = 0 in __init__. A class-level variable is shared by all instances, not stored per object.

Problem 2 — Dog with bark

Problem. Two Dog objects with their own names, each calling its own bark().

How to think about it. Each Dog(name) call creates a separate object with its own self.name. The same method code runs for both, but self refers to a different object each time.

Worked solution.

class Dog:
    def __init__(self, name):
        self.name = name

    def bark(self):
        print(f"{self.name}: Woof!")

rex = Dog("Rex")
lassie = Dog("Lassie")

rex.bark()      # Rex: Woof!
lassie.bark()   # Lassie: Woof!

Common mistakes.

  • Using a global name variable inside bark instead of self.name. Both dogs would print the same name.

Problem 3 — Calculator object

Problem. A Calc object with arithmetic methods that update an internal value.

Worked solution.

class Calc:
    def __init__(self):
        self.value = 0

    def add(self, n):
        self.value = self.value + n

    def sub(self, n):
        self.value = self.value - n

    def mul(self, n):
        self.value = self.value * n

    def show(self):
        print(f"Value: {self.value}")

calc = Calc()
calc.add(10)
calc.show()    # Value: 10
calc.mul(3)
calc.show()    # Value: 30
calc.sub(5)
calc.show()    # Value: 25

Common mistakes.

  • Returning the new value from each method instead of updating self.value. Both are fine, but the spec asks for in-place updates.

Challenge — Stack

Problem. A LIFO stack with push, pop, peek, size.

Worked solution.

class Stack:
    def __init__(self):
        self.items = []

    def push(self, v):
        self.items.append(v)

    def pop(self):
        return self.items.pop()

    def peek(self):
        if len(self.items) == 0:
            return None
        return self.items[-1]

    def size(self):
        return len(self.items)

stack = Stack()
stack.push("a")
stack.push("b")
stack.push("c")
print(stack.size())    # 3
print(stack.peek())    # c
print(stack.pop())     # c
print(stack.pop())     # b
print(stack.size())    # 1

list.append() and list.pop() without an index work at the end of the list — the right place for stack operations.

Common mistakes.

  • Calling self.items.pop(0) — that removes from the front, making it a queue, not a stack, and it costs more because every other item shifts.
  • Not guarding peek — calling self.items[-1] on an empty list raises an IndexError.
  • Forgetting to return from pop and peek. The default return is None, which silently breaks the test.

Done?

The next chapter takes this pattern further. With a proper class, you stop writing __init__ boilerplate for every object individually and share method definitions across many instances that you create cleanly with a constructor call.