Fall 2026
  • Discord
  • Gradescope
  • Syllabus

On this page

  • From dict to class
  • Building a User
  • Building a Tweet
  • Making objects printable and comparable
  • Duck typing
  • When to use classes
  • Submitting

Lab: Classes

Due: Wednesday, October 28 at 11:59pm (one week after it is assigned) Worth: 4 points

This is the second of this week’s two labs; the other is Pull Requests. That lab covers git collaboration. This one asks you to write a class.

The final project is a Twitter clone. Its users and tweets are bundles of data with related operations, which classes can represent. In this lab, you will build User and Tweet classes. The final project will create these objects from database rows and render them on web pages.

Download: lab_classes.py

From dict to class

The reading represented a tweet as a dictionary:

tweet = {'username': 'rtealwitter', 'text': 'my first tweet', 'likes': 0}

The dictionary contains the data, but its operations live elsewhere, and Python identifies it only as a dictionary. A class defines a distinct type and keeps operations such as liking, unliking, and listing hashtags with the data they use.

Both classes in this lab start with a constructor that gives each new instance its initial data. The constructor is the method named __init__, which Python runs the moment you build an object:

class Tweet:
    def __init__(self, author, text):
        self.author = author
        self.text = text
        self.likes = 0

self is the instance being built, which Python supplies automatically. The three assignments give every tweet an author, text, and zero likes. Use the same pattern for both classes: set each object’s initial data in __init__.

Meme: a British developer shown a Python constructor for the first time, captioned 'that is an __init__, innit?'

Building a User

Start with User, the simpler of the two. A user has a username and a follower count, and the count changes over time, so the class needs methods that mutate the instance.

Open lab_classes.py and fill in the User class so that:

  • __init__ takes a username, stores it on self.username, and starts self.followers at 0.
  • follow() adds one to self.followers. A method reaches the object’s data through self, so self.followers = self.followers + 1 reads and writes the instance on which you called the method.
  • unfollow() removes a follower, but never lets the count go negative. Use an if to subtract only when the current count is greater than zero.

The doctests specify the behavior, including the floor at zero:

>>> u = User('alice')
>>> u.follow()
>>> u.follow()
>>> u.unfollow()
>>> u.followers
1
>>> u.unfollow()
>>> u.unfollow()
>>> u.followers
0

Repeated unfollow calls leave a count of zero at zero. This check prevents an invalid follower count.

Building a Tweet

Fill in Tweet so that:

  • __init__ takes an author and some text, stores both, and starts self.likes at 0.
  • like() adds one like; unlike() removes one, with the same never-below-zero floor you wrote for unfollow.
  • hashtags() returns a list of the hashtags in the tweet’s text, each without its leading #.

The hashtags method uses string operations you already know. Split the text into words and select those that start with #:

>>> Tweet('alice', 'learning #python and #oop today').hashtags()
['python', 'oop']
>>> Tweet('bob', 'just a normal tweet').hashtags()
[]

Remove the leading # from each result. A tweet with no tags returns an empty list.

Making objects printable and comparable

By default, printing an object shows its type and memory address:

>>> print(User('alice'))
<__main__.User object at 0x10c3f9d90>

Two dunder methods (double-underscore methods, like __init__) customize this built-in behavior. __str__ controls what print shows, and __eq__ controls what == means. Fill them in so that:

  • str(User('alice')) is '@alice', and str(Tweet('alice', 'hello world')) is '@alice: hello world'. Each __str__ builds and returns the displayed string.
  • Two users are equal when they share a username: User('alice') == User('alice') is True, and User('alice') == User('bob') is False.

__eq__ defines what equality means for the class. If the Twitter clone loads the same account twice from the database, the resulting objects should compare equal.

>>> User('alice') == User('alice')
True

Duck typing

Python often uses an object’s available operations instead of checking its declared type. A function that shows a post cares only that its argument has a .text:

def show(post):
    print(post.text)

Nothing in show says “this must be a Tweet.” It runs on a Tweet, Comment, or Message as long as the object has a .text attribute. This is duck typing: Python uses the operations an object supports rather than its declared type.

Duck typing meme: if it has a .text, prints like it has a .text, and quacks like it has a .text, Python calls it a tweet and gets on with its day.

When to use classes

Classes are often overused. A function does not need to be wrapped in a class unless it operates on data that belongs with it.

Edsger Dijkstra once wrote, “Object-oriented programming is an exceptionally bad idea which could only have originated in California.”

Meme of Edsger Dijkstra captioned with his line that object-oriented programming is an exceptionally bad idea which could only have originated in California.

Use a class when data and behavior belong together. A User combines a username with user operations, and a Tweet combines text with tweet operations. Those relationships justify the two classes in this lab.

Submitting

This lab is auto-graded. Open lab_classes.py, fill in one method body at a time, and run the doctests:

$ python3 -m doctest lab_classes.py

No output means every test passed. When a test fails, the command prints the example with its Expected and Got values; add -v to display passing tests too. Fix each failure and rerun the tests.

Commit the finished file and push it to GitHub:

$ git add lab_classes.py
$ git commit -m 'complete lab_classes.py'
$ git push

Your repository runs the doctests automatically on every push. That action is preliminary feedback, not the grade. Once it is green, submit the repository and branch to the Gradescope Programming Assignment. Gradescope runs instructor-owned pytest cases against lab_classes.py, including unseen state and equality cases, and its result is authoritative. Fix, push, and resubmit until those tests pass.