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 = 0self 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__.
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 ausername, stores it onself.username, and startsself.followersat0.follow()adds one toself.followers. A method reaches the object’s data throughself, soself.followers = self.followers + 1reads and writes the instance on which you called the method.unfollow()removes a follower, but never lets the count go negative. Use anifto 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
0Repeated 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 anauthorand sometext, stores both, and startsself.likesat0.like()adds one like;unlike()removes one, with the same never-below-zero floor you wrote forunfollow.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', andstr(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')isTrue, andUser('alice') == User('bob')isFalse.
__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')
TrueDuck 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.
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.”
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.pyNo 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 pushYour 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.