Objects & Classes
For weeks now, every value we have written came with a type Python handed us: int, float, str, bool, list, dict. We never made a type of our own; we used the ones that came in the box. Today we make our own. The class keyword lets us define a brand-new type, shaped to whatever we are modeling, and the style of programming built around it is called object-oriented programming, or OOP (pronounced “oh oh pee”).
Why would we want a new type? Because the things we actually build are rarely a lone number or string; they are bundles of related data with operations that belong to them. Take a single tweet, since a Twitter clone is where this whole course is heading (that is the final project). A tweet is a username, some text, and a like count, all traveling together, plus the things you can do to it, like liking it.
We already have one tool for bundling data: the dictionary. We could write a tweet as a dict:
tweet = {'username': 'rtealwitter', 'text': 'my first tweet', 'likes': 0}That holds the three pieces together, but it has two problems. First, the operations live somewhere else: to like this tweet you call some separate like(tweet) function that has no built-in connection to the data it changes. Second, Python has no idea this is a tweet. Ask it and you learn only that you have a dictionary:
>>> type(tweet)
<class 'dict'>A class fixes both problems: it bundles the data and the operations under one name, and it gives that bundle a type of its own.
A first class
Think of a class as a container. The simplest possible one is empty:
class Tweet:
passclass introduces a new type named Tweet, and pass is a do-nothing placeholder for the body. By convention a class name is capitalized (more on that at the end), which is why it is Tweet and not tweet. That one line is already enough to create objects of the new type:
>>> tweet = Tweet()
>>> type(tweet)
<class '__main__.Tweet'>Tweet() builds a new object, and type confirms that Python now recognizes a type it did not know a minute ago. The vocabulary here is worth pinning down, because it recurs all term. An object is what a variable refers to, so the variable tweet refers to one. An instance is an object whose type is a particular class, so tweet is an instance of Tweet. “Object” and “instance” name the same thing from two angles: what it is, and which class it came from.
This tweet is a container with nothing in it yet. Next we fill it.
The constructor
We want every tweet to start life with a username, some text, and zero likes. The place to set that up is a special method named __init__, the constructor, which Python runs automatically the moment an instance is created:
class Tweet:
def __init__(self, username, text):
self.username = username
self.text = text
self.likes = 0__init__ is defined like any function, with def, but the double underscores front and back mark it as special (we will come back to those). Its first parameter, self, is the instance being built; Python passes it in for you, and every method receives it. The three body lines attach data to that instance: self.username = username stores the username we were handed onto the object, the next line does the same for the text, and self.likes = 0 starts every tweet at zero likes.
The variables stored on the object, username, text, and likes, are called attributes (some languages call them properties). Now building a tweet reads like calling Tweet with the data we want, and we can pull each attribute back out with a dot:
>>> tweet = Tweet('rtealwitter', 'my first tweet')
>>> tweet.username
'rtealwitter'
>>> tweet.likes
0Notice we passed two arguments, 'rtealwitter' and 'my first tweet', but __init__ lists three parameters. The first, self, is not something we pass; Python fills it in with the new object automatically. That off-by-one between the parameters you write and the arguments you pass is the most common source of confusion with self, so hold onto it: self is the instance, supplied for free, and your arguments line up with everything after it.
Methods
Attributes are the data; methods are the operations that belong to it. A method is a function defined inside the class, and like __init__ its first parameter is self, the instance it acts on. Let’s give a tweet the one operation every tweet needs:
class Tweet:
def __init__(self, username, text):
self.username = username
self.text = text
self.likes = 0
def like(self):
self.likes = self.likes + 1like takes only self and adds one to that instance’s like count. Because it reaches the data through self, it always likes the exact tweet you call it on, and no other. We call a method with the same dot, and this time the dot is doing real work:
>>> tweet = Tweet('rtealwitter', 'my first tweet')
>>> tweet.like()
>>> tweet.like()
>>> tweet.likes
2When you write tweet.like(), Python passes tweet in as self, so the two calls raise this tweet’s likes from 0 to 2. That is the point of a class: the data (likes) and the operation on it (like) live together under one type, and the method changes the specific object you invoked it on. What do you think tweet.like() returns, as opposed to what it changes? It returns None; its job is the side effect on self.likes, which is why the REPL prints nothing after each call.
Dunder methods
__init__ was our first method with double underscores front and back. Methods named that way are called dunder methods (short for “double underscore”) or magic methods, and Python calls them for you at special moments rather than by name. You never write tweet.__init__(...) yourself; Python calls __init__ when you write Tweet(...). There is a whole family of them for hooking into Python’s built-in behavior: __str__ controls what print shows, and __eq__ controls what == means. You will meet more of them when we build the Twitter clone; for today, __init__ is the one that matters, the constructor that gives every new object its starting data.
Fittingly, that joke arrives as a tweet.
Duck typing
Here is something that surprises people coming from other languages. Python never checks an object’s type before using it; it just tries whatever you asked for and sees whether it works. Consider a function that shows a post:
def show(post):
print(post.text)Nothing in show says “this must be a Tweet.” It runs on any object that happens to have a .text attribute, and fails on any object that does not. This is called duck typing: if an object walks like a duck and quacks like a duck, Python treats it as a duck.
The practical result is that show would work unchanged on a Tweet, a Comment, or a Message, as long as each has a .text. Python cares about what an object can do, not what it was declared to be.
Private attributes
In many languages some attributes are private, walled off so only the class’s own methods can touch them. Python has no such wall: every attribute is reachable from everywhere, always. What Python has instead is a convention. If an attribute is meant for internal use only, you prefix its name with a single underscore, like self._likes, as a note to other programmers that reads “this is internal, change it at your own risk.” Nothing stops them; the underscore is a social signal, not a lock.
Programmers hold strong opinions about how much of this bookkeeping is worth doing, which the internet has documented at length:
When to use a class
Now the honest part. Classes are useful, and they are also badly overused. Once you learn them, it is easy to wrap every stray function in a class that did not need one, and a lot of real-world code is worse for exactly that habit.
The quote is Edsger Dijkstra’s, and the sentiment is common among experienced programmers. Reach for a class when you genuinely have data and behavior that belong together, like our tweet; leave a plain function a plain function otherwise. A useful tell: even Automate the Boring Stuff, the book this course leans on, never needs a chapter on classes. You can automate an enormous amount without ever writing class yourself. The goal today is to recognize a class when you see one, because you will see them constantly in other people’s code and in the libraries we use, not to turn everything you write into one. If you want a fuller tour, Real Python’s object-oriented programming guide is a good next stop.
Naming conventions
One loose end from all this code. Python has an official style guide, PEP 8 (a “Python Enhancement Proposal”), and its naming rule is worth memorizing, because breaking it marks code as amateur at a glance. Class names use CamelCase: capitalize each word and run them together, like Tweet or HttpResponse. Everything else, meaning variables, functions, methods, and attributes, uses snake_case: lowercase words joined by underscores, like username, like, and input_name. Python never uses lowerCamelCase (capitalized words but a lowercase first letter, like inputName), even though other languages lean on it heavily. If you see it in Python, someone brought a habit over from Java.
Looking forward
You can now define a type of your own with class, give each instance its data in __init__, and act on that data with methods. That is the toolkit behind every application we build from here, most of all the Twitter clone in the final project, where a User and a Tweet are classes much like the one above and the whole site is the story of their objects.
This week’s lab steps sideways, to the skill that lets you build any of that with other people: git. So far you have used git alone, to save and submit your own work. The Pull Request tutorial adds the collaborative half, branching and merging and opening a pull request to contribute a change to someone else’s repository, which is how essentially all software gets written.