Skip to main content

Classes and instances

Every value is an object with a type, and a class is how you make a type of your own. Write a Student class with __init__, see what self is, and draw each instance as a small table of its own attributes.
P01.9.1L0 Foundation25 minIn review

Before this lesson

P01.8 Errors, debugging and first tests P01.5.1 Modules and import P01.6.3 Dictionaries and sets

After this lesson you can

  • Say what a class and an instance are, and make instances by calling a class.
  • Write a class with an __init__ method that gives every new instance its own attributes through self.
  • Read and set attributes with the dot, and draw a class and its instances in the memory diagram.
  • Inspect an object with vars, type and isinstance.

In review. The maths and code on this page are checked by automated tests on every change, but an independent reader has not signed it off yet. Spot a mistake or a confusing step? Tell us.

The question

So far a student of the study table has been a tuple, ('Ama', 2, 55), or a dictionary, {'name': 'Ama', 'hours': 2, 'score': 55}. In the tuple you must remember that the score sits at position 2, counting from 0; in the dictionary a typing slip such as row['scroe'] is found only when that line runs. Every value you have met, 55, 'Ama' or a list, is an object of some type, and its type decides what it can do. Can you make a type of your own, Student, so that ama.score says what it means?

From the last lesson

1. What does assert condition do when the condition is true, and when it is false?

Show answer

When it is true, nothing. When it is false, it raises AssertionError.

2. Why should the expected value of a test be worked out by hand?

Show answer

If it is copied from the code’s own output, the test checks the code against itself and passes on the bug.

Intuition

A class is a type that you make. Calling it makes a new object of that type, an instance of the class. Each instance holds a small table of names of its own, its attributes, and each name refers to an object, exactly like the names of a module in P01.5.1.

A memory diagram. On the left, a frame of global names holds Student, ama and ben. Student points to a class box titled Student, which holds the name __init__, pointing to a function object. ama points to a Student object, a box listing three attributes with their values: name 'Ama', hours 2 and score 55, with score on a yellow band. ben points to a second Student object with name 'Ben', hours 4, score 65. A dashed arrow runs from each Student object to the class box. A note reads: ama.score, follow the arrow from ama, then look up score in its table: 55.
P01.9.1-F1

One class object and two instances. Each instance is its own small table of attribute names, and each points back to its class. ama.score follows the arrow from ama and reads score in that table.

Three things in the picture carry the whole lesson.

  1. One class, many instances. The class Student exists once. Every call Student(...) makes a new instance.
  2. Each instance keeps its own data. ama and ben have the same attribute names but their own values, so changing ben.score leaves ama alone.
  3. The dot reads a name inside a box. ama.score means: follow ama to its object, then look up score there. It is the same dot as in math.sqrt.

Build it up

Step 1. Every value already has a type. The built-in type tells you which. The type is itself an object, a class, which is why Python prints the word class.

print(type(55))
print(type('Ama'))
print(type([55, 65]))
<class 'int'>
<class 'str'>
<class 'list'>

int, str and list are classes that come with Python. A class statement makes one more.

Step 2. The smallest class. The body of a class may be just pass, a statement that does nothing. Calling the class makes an empty instance, and an assignment with a dot gives it an attribute. The built-in vars shows an object’s table of attributes as a dictionary.

class Student:
    pass


ama = Student()
print(vars(ama))
ama.name = 'Ama'
ama.score = 55
print(vars(ama))
print(ama.score)
{}
{'name': 'Ama', 'score': 55}
55

Attributes “spring into existence when they are first assigned to”, as the Python Tutorial puts it (9.3.3), just as a name does with =. But filling every student in by hand, line by line, is slow and easy to get wrong: forget one line and that student has no score.

Step 3. __init__ fills in every new instance the same way. A function written inside a class is a method. A method named __init__ (two underscores on each side) is special: when you call the class, Python first makes a new, empty instance, then calls __init__ with that instance as the first argument. By convention the first parameter is called self, so inside __init__, self is the new object being filled in.

A call stack and the objects it points to, during the call Student('Ama', 2, 55). At the top, the global frame, waiting at line 7, holds Student, which points to the class box, and the note ama: not bound yet. Below it the frame of __init__, running line 5, holds self, name 'Ama', hours 2 and score 55. An arrow runs from self to a new Student object whose table already holds name 'Ama' and hours 2, and whose third row, score 55, is on a yellow band because line 5 is writing it. A dashed arrow runs from the new object to the class box.
P01.9.1-F2

Inside __init__, self is the new object. Each line self.<name> = <value> writes one row of its table; here line 5 writes score.

class Student:
    def __init__(self, name, hours, score):
        self.name = name
        self.hours = hours
        self.score = score


ama = Student('Ama', 2, 55)
print(ama.name, ama.hours, ama.score)
print(vars(ama))
Ama 2 55
{'name': 'Ama', 'hours': 2, 'score': 55}

You pass three arguments, but __init__ has four parameters: Python passes the new object to self for you. self.name = name reads the parameter name on the right and writes the attribute name of the new object on the left. The two share a spelling, which is usual, but they are different names in different places: one in the frame of the call, one in the object’s table.

Step 4. Each instance keeps its own data. Make a second student and change one attribute.

ben = Student('Ben', 4, 65)
ben.score = 70
print(ama.score, ben.score)
55 70

The formal statement

NoteDefinitions: class, instance, attribute, __init__, self

A class is an object that makes new objects; the class statement creates it and binds its name. An instance of a class is an object made by calling the class. An attribute is a name that belongs to an object, written object.name. __init__ is the method that Python calls on every new instance, and self is the name, by convention, of its first parameter, which receives that instance.

What class Student: does. Like def, it runs once and binds a name. Python runs the indented body, which here defines the function __init__, collects the names it defined into a new class object, and binds Student to that object (Tutorial, 9.3.1).

What Student('Ama', 2, 55) does, in four steps.

  1. Make a new, empty object whose class is Student.
  2. Call __init__ with that object first and the arguments after it: self is bound to the new object, name to 'Ama', hours to 2, score to 55.
  3. Run the body. Each self.<attribute> = ... adds a row to the object’s table. __init__ returns None.
  4. The value of the whole call is the new object, which ama = ... then binds to ama.

Reading an attribute. To work out ama.score, Python evaluates ama to an object and looks up score among that object’s attributes. If the name is not there (or, as the next lesson shows, in its class), Python raises AttributeError. Setting an attribute, ama.score = 60, binds the name score in the object’s table, adding the row if it was not there.

Looking inside. Three built-ins describe an object:

  • vars(ama) returns the object’s table of attributes as a dictionary.
  • type(ama) returns the class the object was made from, so type(ama) is Student is True.
  • isinstance(ama, Student) asks whether ama is an instance of Student.
On the left, the Student object of ama with its three rows: name, hours and score. An arrow labelled vars(ama) leads to a dictionary drawn as a two-column table with keys 'name', 'hours' and 'score' and values 'Ama', 2 and 55. Below, three lines of code with their results: type(ama) is Student gives True, isinstance(ama, Student) gives True, and isinstance(55, Student) gives False.
P01.9.1-F5

vars shows the instance’s table as a dictionary; type and isinstance ask which class an object belongs to.

Worked example

Trace this program line by line. The line numbers are those of the listing in the figure.

class Student:
    def __init__(self, name, hours, score):
        self.name = name
        self.hours = hours
        self.score = score

ama = Student('Ama', 2, 55)
ben = Student('Ben', 4, 65)
ben.score = 70
print(ama.score, ben.score)
55 70
A listing of ten lines and a trace table under it. Line 1 runs the class statement and binds Student to the class. Line 7 calls Student('Ama', 2, 55): a new empty object is made and __init__ is called with self bound to it. Lines 3, 4 and 5 add name 'Ama', hours 2 and score 55 to its table. Back at line 7 the call's value, the object, is bound to ama. Line 8 does the same for Ben, with hours 4 and score 65, and binds ben. Line 9 rebinds ben.score to 70 and leaves ama alone. Line 10 prints 55 70.
P01.9.1-F3

The trace of the worked example: one row per line run. The rows for Ben’s __init__ are folded into one, because they repeat Ama’s.

Reading the trace:

  • Line 1 runs once. It does not make a student; it makes the class.
  • Line 7 is where the work starts. Python makes an empty object and jumps into __init__ with self bound to it. Lines 3 to 5 add three rows to its table. Only when __init__ returns does ama get bound, so during lines 3 to 5 the name ama does not exist yet.
  • Line 8 repeats the four steps for Ben, with a second, separate object.
  • Line 9 changes one row of Ben’s table. Ama’s table is a different object, so ama.score is still 55, and line 10 prints 55 70.

Now the whole study table. Walk the three lists together with zip (P01.6.2), make one Student for each row, and keep them in a list. Then use the attributes in the loop patterns of P01.3.4.

names = ['Ama', 'Ben', 'Chen', 'Dina', 'Eli']
hours = [2, 4, 5, 1, 3]
scores = [55, 65, 80, 40, 60]

students = []
for name, h, s in zip(names, hours, scores):
    students.append(Student(name, h, s))

total = 0
passes = 0
for student in students:
    total += student.score
    if student.score >= 50:
        passes += 1
print(len(students), 'students, mean score', total / len(students), 'and', passes, 'passes')
5 students, mean score 60.0 and 4 passes

By hand: \({55 + 65 + 80 + 40 + 60} = 300\), so the mean is \({300 / 5} = 60.0\), and every score but Dina’s 40 reaches 50, so there are 4 passes. Each row now travels as one object, and the code says student.score where it once said row[2].

Code

The study table usually arrives as a file. csv.DictReader (P01.7.4) gives each row as a dictionary of text; the class turns it into a Student with whole numbers.

import csv
import pathlib
import tempfile

with tempfile.TemporaryDirectory() as folder:
    path = pathlib.Path(folder, 'study.csv')
    path.write_text('name,hours,score\nAma,2,55\nBen,4,65\nChen,5,80\nDina,1,40\nEli,3,60\n',
                    encoding='utf-8')
    with open(path, encoding='utf-8') as file:
        rows = list(csv.DictReader(file))

students = []
for row in rows:
    students.append(Student(row['name'], int(row['hours']), int(row['score'])))

chen = students[2]
print(rows[2])
print(vars(chen))
print(type(chen) is Student, isinstance(chen, Student), isinstance(rows[2], Student))
print(type(chen).__name__, type(rows[2]).__name__)
{'name': 'Chen', 'hours': '5', 'score': '80'}
{'name': 'Chen', 'hours': 5, 'score': 80}
True True False
Student dict

The dictionary from the file and the instance hold the same facts, and vars shows that an instance’s attributes really are kept in a dictionary. What the class adds is a type of its own: isinstance can tell a Student from any other dictionary, and the next lessons give the class methods that work on its data. (type(chen).__name__ gives the class’s name alone; printing type(chen) would also show where the class was defined, which depends on how the code is run.)

Explore

This module’s interactive, the instance workbench, lives in P01.9.2. It draws one class, RunningMean, and two instances, a and b, in the same way as this lesson’s anchor figure. Before you open it, predict: if you change an attribute of a, what happens to b?

Pitfalls

Two rows. Wrong, with a cross: a class whose __init__ has the line name = name. Beside it the Student object after ama = Student('Ama') has an empty table, marked no attributes, and the line ama.name gives AttributeError, in reddish purple. The note says name = name binds a local name in the frame of __init__, which is thrown away when the call returns. Right, with a tick: the same class with self.name = name. The Student object holds name 'Ama', and ama.name gives 'Ama'.
P01.9.1-F4

Wrong beside right. Without self., the assignment binds a local name of the call, which vanishes when __init__ returns. With self., it writes into the new object.

“Inside __init__, name = name stores the name.”

It binds a local name in the frame of __init__ (P01.4.4), and that frame is thrown away when the call returns. The object’s table stays empty, and ama.name later raises AttributeError. Every attribute is written with self. in front.

“I can leave self out of the parameters, since I never pass it.”

Python always passes the new object as the first argument, so def __init__(name, hours, score) receives four arguments for three parameters, and Student('Ama', 2, 55) stops with TypeError: Student.__init__() takes 3 positional arguments but 4 were given (Python 3.15; the wording varies a little between versions).

“ama = Student makes a student.”

Without the brackets, nothing is called: ama becomes a second name for the class itself. Then ama.score = 55 gives the class an attribute, and no student has been made at all. Call the class to make an instance: Student('Ama', 2, 55).

“Two instances of one class share their data.”

They share the class, not the table. In machine learning this is the everyday case: two models made from one class, say two LinearRegression() objects of scikit-learn (P01.9.4), each learn and keep their own numbers, which is why one can be trained on one dataset and the other on another.

Check yourself

Recall

1. What is the difference between a class and an instance?

Show answer

A class is a type you define, made once by the class statement. An instance is one object made by calling the class; there can be many.

2. What is self inside __init__?

Show answer

The new instance that is being filled in. Python passes it as the first argument when the class is called.

3. Name the four steps of Student('Ama', 2, 55).

Show answer

Make a new empty object of class Student; call __init__ with it as self and the three arguments; run the body, which writes the attributes; give back the new object as the value of the call.

4. What does vars(ama) return?

Show answer

A dictionary of the instance’s attributes, here {'name': 'Ama', 'hours': 2, 'score': 55}.

5. After ben.score = 70, why is ama.score still 55?

Show answer

ama and ben are two objects, each with its own table. The assignment rebinds score in Ben’s table only.

Exercises

1. ★ Write a class Course whose __init__ takes a code and a title and stores both. Make p01 = Course('P01', 'Python from Zero') and print its title.

Show solution
class Course:
    def __init__(self, code, title):
        self.code = code
        self.title = title


p01 = Course('P01', 'Python from Zero')
print(p01.title)          # Python from Zero

vars(p01) is {'code': 'P01', 'title': 'Python from Zero'}.

2. ★★ Predict what this prints, and draw the memory diagram at the end.

dina = Student('Dina', 1, 40)
eli = Student('Eli', 3, 60)
dina.score = dina.score + 10
eli.hours = dina.hours
print(dina.score, eli.score, eli.hours)
Show solution

It prints 50 60 1. Line 3 reads Dina’s score, 40, adds 10 and writes 50 back into Dina’s table. Line 4 reads Dina’s hours, 1, and binds hours in Eli’s table to it. Eli’s score is untouched. In the diagram, dina points to a Student object with name 'Dina', hours 1, score 50; eli to another with name 'Eli', hours 1, score 60; both point to the one class Student.

3. ★★★ Using the list students of the worked example, write a loop that finds the student with the highest score (best so far, P01.3.4) and the mean of the hours. Work out both by hand first.

Show solution

By hand: the highest score is Chen’s 80, and the hours add up to \({2 + 4 + 5 + 1 + 3} = 15\), so the mean is \({15 / 5} = 3.0\).

best = students[0]
hours_total = 0
for student in students:
    if student.score > best.score:
        best = student
    hours_total += student.hours
print(best.name, hours_total / len(students))     # Chen 3.0

best holds the whole object, not just the score, so its name comes along for free.

Summary card

A class is a type you make; each instance holds its own table of attributes.

The anchor figure again: global names Student, ama and ben; the class Student holding __init__; two Student objects, one with name 'Ama', hours 2, score 55 and one with name 'Ben', hours 4, score 65, each with a dashed arrow to the class.
P01.9.1-F1

One class, two instances.

  • Define: class Student: with def __init__(self, ...): and self.name = name.
  • Make: ama = Student('Ama', 2, 55) makes an object, runs __init__ on it, returns it.
  • Use: ama.score reads an attribute, ama.score = 60 sets one.
  • Inspect: vars(ama), type(ama) is Student, isinstance(ama, Student).

Sources and next steps

Sources. Each was opened and checked against this lesson on 12 November 2026.

  • Python Software Foundation, The Python Tutorial, chapter 9, Classes: 9.3.1, that leaving a class definition creates a class object and binds it to the class name; 9.3.2, that “class instantiation automatically invokes __init__() for the newly created class instance”; 9.3.3, that data attributes “spring into existence when they are first assigned to”; and 9.4, that the name self is “nothing more than a convention”.
  • Python Software Foundation, The Python Standard Library, Built-in Functions: vars returns the __dict__ attribute of an instance, type with one argument returns the type of an object, and isinstance returns True if the object is an instance of the class or of a subclass.
  • Allen B. Downey, Think Python, 3rd edition, chapter 14, Classes and Functions: a class as “a programmer-defined type”, an instance as “an object that belongs to a class”, an attribute as “a variable associated with an object”, and object diagrams of the kind drawn here.

Next. P01.9.2 gives a class methods of its own: functions that live once in the class and work on whichever instance calls them.

This lesson unlocks

P01.9.2 Methods and state

Your progress is saved in this browser only. No account, no tracking.
Back to top