Methods and self
A school has one set of rules for every locker, "open with your own key," but each student's key only opens their own locker.
In Classes and Objects, self appeared everywhere: as the first parameter of every method, and in front of every attribute. It can feel like typing a strange word for no reason. This lesson shows exactly what self is, why Python asks you to write it, and the two mistakes that come from forgetting it.
The locker rules
Every student in a school follows the same locker rules: "Open your locker with your key. Put your books inside. Close it." The rules are written once, for everyone.
But when Maria follows them, she opens her locker, with her key. When Ben follows them, he opens his. The rules say "your," and "your" means whoever is following them right now.
In a class, the methods are the rules, written once. self is the word "your": it means whichever object the method is working on right now.
What Python does behind the scenes
When you write this:
class Student:
def __init__(self, name):
self.name = name
def greet(self, greeting):
print(f"{greeting}, I'm {self.name}.")
maria = Student("Maria")
maria.greet("Hello")Hello, I'm Maria.Python quietly turns maria.greet("Hello") into this:
Student.greet(maria, "Hello")Hello, I'm Maria.The object before the dot, maria, is passed in as the first argument, and it lands in the first parameter: self. Then "Hello" lands in greeting. That's all self is: an ordinary parameter that holds the object the method was called on.
self isn't a keyword. You could technically call it anything, but every Python programmer uses self, so always use it too.
Mistake 1: forgetting self in the definition
If a method is defined without self, Python still passes the object in, and there's nowhere to put it:
class Student:
def greet():
print("Hello!")
maria = Student()
maria.greet() # error: TypeError: Student.greet() takes 0 positional arguments but 1 was given"But I didn't give it any arguments!" Python did: the object, maria. Whenever you see "takes 0 positional arguments but 1 was given" on a method, check for a missing self.
Mistake 2: forgetting self. in front of an attribute
Inside a method, attributes must always be reached through self. A plain name means a local variable:
class Student:
def __init__(self, name):
self.name = name
def greet(self):
print(f"Hi, I'm {name}.")
Student("Maria").greet() # error: NameError: name 'name' is not definedname was a parameter of __init__, and it vanished when __init__ finished, just like any local variable from Variable Scope. What survived is the attribute, self.name. Writing self. is how a method reaches into this object's own locker.
The same goes for calling one method from another: write self.average(), not just average().
Methods that change the object
A method can change the object's attributes, and the change lasts after the method ends, because it's stored on the object itself:
class Counter:
def __init__(self):
self.count = 0
def click(self):
self.count += 1
def reset(self):
self.count = 0
door = Counter()
door.click()
door.click()
door.click()
print("Visitors:", door.count)
door.reset()
print("After reset:", door.count)Visitors: 3
After reset: 0This is the clean alternative to the global variables from Variable Scope. The count lives inside its own object, and only the methods that belong with it change it.
Class attributes: shared by everyone
Every attribute so far has been set on self, so each object has its own. You can also create a variable directly inside the class, outside any method. That's a class attribute, and there's only one, shared by every object, like the school bell every classroom hears:
class Student:
school = "Rizal High School"
count = 0
def __init__(self, name):
self.name = name
Student.count += 1
maria = Student("Maria")
ben = Student("Ben")
print(maria.school)
print(ben.school)
print("Students created:", Student.count)Rizal High School
Rizal High School
Students created: 2Every student can read school, and there's only one copy of it. To change a class attribute, go through the class name, like Student.count += 1. Writing self.count += 1 would quietly create a separate count on that one object instead of updating the shared one.
Class attributes are best for constants that belong to the class, like PASSING_SCORE = 75, and for counters like this one. Anything that differs from object to object belongs on self.
Try it
This program runs a small classroom library. Each Book tracks whether it's checked out, and a class attribute counts every checkout across all books. Predict every line.
Running Python here
Running Python right in the browser is coming to Syntaxia soon. For now, you can read and edit the code here. If you have Python installed on your computer (see Setting Up), save it as main.py and run it with python main.py (or python3 main.py on macOS and Linux).
Check your prediction
Maria borrowed 'Noli Me Tangere' for 7 days.
Sorry Ben, 'Noli Me Tangere' is with Maria.
Ben borrowed 'Cosmos' for 7 days.
Maria returned 'Noli Me Tangere'.
Ben borrowed 'Noli Me Tangere' for 7 days.
Total checkouts: 3
Noli is with: Ben
Cosmos is with: BenBen's first try failed because Maria still had the book. Book.check_out(noli, "Ben") is exactly what noli.check_out("Ben") does behind the scenes, with noli passed in as self. The failed attempt returned early, before adding to the count, so there were three real checkouts. self.LOAN_DAYS finds the class attribute, since the object has no attribute of its own with that name.
Try it yourself
- Add a method
is_available(self)that returnsTruewhen the book isn't checked out, and use it incheck_out. - Remove
selffrom the parameters ofgive_back, and predict the error before running it. - Add a class attribute
all_titles = [], and append each new book's title to it in__init__. Print it at the end. Why do all books share one list?
Check your understanding
Up next
A school has students, but it also has teachers, and both are people with a name. Writing the shared parts twice would be a waste. In Inheritance, you'll build one class on top of another.
