Discussion 6: Classes🖨️

Attendance

Your TA will come around during discussion to check you in. You can start on the worksheet before being checked in; you don't need to wait for your TA to get started.

If you didn't attend for a good reason (such as being sick), fill out this form (within 2 weeks of your discussion): attendance form

If you get stuck, there are walkthrough videos (@berkeley.edu login to YouTube required).

Getting Started

In honor of our discussion topic today (Classes), please share your favorite class (other than this one) that you are taking this semester. Make sure you know each others' names by saying yours.

Object-Oriented Programming

A productive approach to defining new classes is to determine what instance attributes each object should have and what class attributes each class should have. First, describe the type of each attribute and how it will be used, then try to implement the class's methods in terms of those attributes.

Q1: Smart Fridge

The SmartFridge class is used by smart refrigerators to track which items are in the fridge and let owners know when an item has run out.

The class internally uses a dictionary to store items, where each key is an item name and each value is the current quantity. The add_item method adds the given quantity of the given item and reports the current quantity. You may assume use_item is only called on items already in the fridge; it uses up the given quantity of that item. If the quantity would fall to or below zero, it should use only up to the remaining quantity and remind the owner to buy more of that item.

Finish the SmartFridge class definition so that its add_item and use_item methods work as specified by the doctests.

You may find Python's formatted string literals, or f-strings, useful:

>>> feeling = 'love'
>>> course = '61A!'
>>> f'I {feeling} {course}'
'I love 61A!'
class SmartFridge:
    """A fridge that tracks how much of each item it holds.

    >>> fridgey = SmartFridge()
    >>> fridgey.add_item('Mayo', 1)
    'I now have 1 Mayo'
    >>> fridgey.add_item('Mayo', 2)
    'I now have 3 Mayo'
    >>> fridgey.use_item('Mayo', 2.5)
    'I have 0.5 Mayo left'
    >>> fridgey.use_item('Mayo', 0.5)
    'Oh no, we need more Mayo!'
    >>> fridgey.add_item('Eggs', 12)
    'I now have 12 Eggs'
    >>> fridgey.use_item('Eggs', 15)
    'Oh no, we need more Eggs!'
    >>> fridgey.add_item('Eggs', 1)
    'I now have 1 Eggs'
    """
    def __init__(self):
        self.items = {}

    def add_item(self, item: str, quantity: float) -> str:
        "*** YOUR CODE HERE ***"

    def use_item(self, item: str, quantity: float) -> str:
        "*** YOUR CODE HERE ***"

Q2: Keyboard

Overview: A keyboard has a button for every letter of the alphabet. When a button is pressed, it outputs its letter by calling an output function (such as print). Whether that letter is uppercase or lowercase depends on how many times the caps lock key has been pressed.

First, implement the Button class, which takes a lowercase letter (a string) and a one-argument output function, such as Button('c', print).

The press method of a Button calls its output attribute (a function) on its letter attribute: either uppercase if caps_lock has been pressed an odd number of times or lowercase otherwise. The press method also increments pressed and returns the button that was pressed. Hint: 'hi'.upper() evaluates to 'HI'.

Second, implement the Keyboard class. A Keyboard has a dictionary called keys containing a Button (with its letter as its key) for each letter in LOWERCASE_LETTERS. It also has a list of the letters typed, which may be a mix of uppercase and lowercase letters.

The type method takes a string word containing only lowercase letters. It invokes the press method of the Button in keys for each letter in word, which adds a letter (either lowercase or uppercase depending on caps_lock) to the Keyboard's typed list. Important: Do not use upper or letter in your implementation of type; just call press instead. Do not use append in your implementation of type either; you must mutate the list typed only using press.

Read the doctests and talk about:

  • Why it's possible to press a button repeatedly with .press().press().press().
  • Why pressing a button repeatedly sometimes prints on only one line and sometimes prints multiple lines.
  • Why bored.typed has 10 elements at the end.

Discussion Time: Before anyone types anything, have a conversation describing the type of each attribute and how it will be used. Start with Button: how will letter and output be used? Then discuss Keyboard: how will typed and keys be used? How will new letters be added to the list called typed each time a Button in keys is pressed? Call the staff if you're not sure! Once everyone understands the answers to these questions, you can try writing the code together.

LOWERCASE_LETTERS = 'abcdefghijklmnopqrstuvwxyz'

class CapsLock:
    def __init__(self):
        self.pressed = 0

    def press(self):
        self.pressed += 1

class Button:
    """A button on a keyboard.

    >>> f = lambda c: print(c, end='')  # The end='' argument avoids a new line
    >>> k, e, y = Button('k', f), Button('e', f), Button('y', f)
    >>> s = e.press().press().press()
    eee
    >>> caps = Button.caps_lock
    >>> t = [x.press() for x in [k, e, y, caps, e, e, k, caps, e, y, e, caps, y, e, e]]
    keyEEKeyeYEE
    >>> u = Button('a', print).press().press().press()
    A
    A
    A
    """
    caps_lock = CapsLock()

    def __init__(self, letter, output):
        assert letter in LOWERCASE_LETTERS
        self.letter = letter
        self.output = output
        self.pressed = 0

    def press(self):
        """Call output on letter (maybe uppercased), then return this button."""
        self.pressed += 1
        "*** YOUR CODE HERE ***"
Button Hint: Uppercasing

Since self.letter is always lowercase, use self.letter.upper() to produce the uppercase version.

Button Hint: Caps Lock

The number of times caps_lock has been pressed is either self.caps_lock.pressed or Button.caps_lock.pressed.

Button Hint: Output

The output attribute is a function that can be called: self.output(self.letter) or self.output(self.letter.upper()). You do not need to return the result.

class Keyboard:
    """A keyboard.

    >>> Button.caps_lock.pressed = 0  # Reset the caps_lock key
    >>> bored = Keyboard()
    >>> bored.type('hello')
    >>> bored.typed
    ['h', 'e', 'l', 'l', 'o']
    >>> bored.keys['l'].pressed
    2

    >>> Button.caps_lock.press()
    >>> bored.type('hello')
    >>> bored.typed
    ['h', 'e', 'l', 'l', 'o', 'H', 'E', 'L', 'L', 'O']
    >>> bored.keys['l'].pressed
    4
    """
    def __init__(self):
        self.typed = []
        self.keys = ...  # Try a dictionary comprehension!

    def type(self, word):
        """Press the button for each letter in word."""
        assert all([w in LOWERCASE_LETTERS for w in word]), 'word must be all lowercase'
        "*** YOUR CODE HERE ***"
Keyboard Hint: Keys

The keys can be created using a dictionary comprehension: self.keys = {c: Button(c, ...) for c in LOWERCASE_LETTERS}. The call to Button should take c and an output function that appends to self.typed, so that every time one of these buttons is pressed, it appends a letter to self.typed.

Keyboard Hint: Type

Call the press method of self.keys[w] for each w in word. When you call press, the Button is already set up (in the Keyboard.__init__ method) to output to the typed list of this Keyboard.

Description Time: Describe how new letters are added to typed each time a Button in keys is pressed. Instead of just reading your code, say what it does (e.g., "When the button of a keyboard is pressed ..."). One short sentence is enough.

Q3: Bear

Implement the SleepyBear and WinkingBear classes so that calling their print method matches the doctests. Use as little code as possible and try not to repeat any logic from Eye or Bear. Each blank can be filled with just two short lines.

class Eye:
    """An eye.

    >>> Eye().draw()
    '0'
    >>> print(Eye(False).draw(), Eye(True).draw())
    0 -
    """
    def __init__(self, closed=False):
        self.closed = closed

    def draw(self):
        if self.closed:
            return '-'
        else:
            return '0'

class Bear:
    """A bear.

    >>> Bear().print()
    ? 0o0?
    """
    def __init__(self):
        self.nose_and_mouth = 'o'

    def next_eye(self):
        return Eye()

    def print(self):
        left, right = self.next_eye(), self.next_eye()
        print('? ' + left.draw() + self.nose_and_mouth + right.draw() + '?')
class SleepyBear(Bear):
    """A bear with closed eyes.

    >>> SleepyBear().print()
    ? -o-?
    """
    "*** YOUR CODE HERE ***"

class WinkingBear(Bear):
    """A bear whose left eye is different from its right eye.

    >>> WinkingBear().print()
    ? -o0?
    """
    def __init__(self):
        "*** YOUR CODE HERE ***"

    def next_eye(self):
        "*** YOUR CODE HERE ***"
Hint: Inheritance

A subclass can override a method like next_eye just by defining it again. If the subclass defines its own method, but still needs the parent's version, you can call the parent's method with super().method_name(...).

Exam Practice

Object-oriented programming problems often appear on exams.

Q4: Counter

Fall 2024 Final Exam Question 4(a): Implement the Counter class. A Counter has a count of the number of times inc has been invoked on itself or any of its offspring. Its offspring are the Counters created by its spawn method or the spawn method of any of its offspring.

class Counter:
    """Counts how many times inc has been called on itself or any of its spawn.

    >>> total = Counter()
    >>> odd, even = total.spawn(), total.spawn()
    >>> one, three = odd.spawn(), odd.spawn()
    >>> for c in [one, even, three, even, odd, even]:
    ...     c.inc()
    >>> [c.count for c in [one, three, even, odd, total]]
    [1, 1, 3, 3, 6]
    """
    def __init__(self, parent=None):
        self.parent = parent
        _______________

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

    def spawn(self):
        return _______________

Q5: MissDict

Fall 2024 Final Exam Question 4(b): Implement the MissDict class. A MissDict has a dictionary d. Its get method takes an iterable keys, returns a list of all values in d that correspond to those keys, and counts the number of keys that did not appear in d (called misses). Printing a MissDict displays a fraction in which:

  • The numerator is the number of misses during all calls to get for that particular MissDict instance.
  • The denominator is the number of misses during all calls to get for any MissDict instance.

Reminder: When you print an object (or call str on it), Python calls that object's __str__ method and displays the string it returns.

Assume Counter is implemented correctly.

class MissDict:
    """Has a dict, gets a list of values for an iterable of keys, and counts keys that are not in the dict.

    >>> double = MissDict({1: 2, 2: 4, 3: 6, 5: 10})
    >>> half = MissDict({2: 1.0, 3: 1.5, 4: 2.0})
    >>> double.get([1, 3, 5, 2, 4])  # No value for key 4 (1 miss)
    [2, 6, 10, 4]
    >>> double.get([5, 4, 3, 0, 4])  # No value for keys 0 or either 4 (3 misses)
    [10, 6]
    >>> half.get([1, 3, 5, 2, 4])    # No value for keys 1 or 5 (2 misses)
    [1.5, 1.0, 2.0]
    >>> print(double)
    4/6 of the misses
    """
    misses = Counter()
    def __init__(self, d):
        assert isinstance(d, dict)
        self.d = d
        self.misses = _______________

    def get(self, keys):
        result = []
        for k in keys:
            if k in self.d:
                _______________
            else:
                _______________
        return result

    def __str__(self):
        return f'_______________________________ of the misses'

Back to Top

Accessibility Nondiscrimination

Copyright ©2026, Regents of the University of California and respective authors.

This site is built following the Berkeley Class Site template, which is generously based on the Just the Class, and Just the Docs templates.

View all course offerings