C.W.K.
Stream
Lesson 01 of 07 · published

Defining a Class — class, __init__, self

~22 min · class, init, self, instance

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete

The class statement

A class statement creates a class object — itself a value, like everything in Python — and binds it to a name. Inside the body, you define methods (functions that take self as the first argument) and class-level attributes. Calling the class (Circle(3)) creates an instance.

__init__ — the post-construction setup, not the constructor

__init__ isn't technically the constructor. The actual constructor is __new__, which creates the bare instance. __init__ is the setup hook that runs immediately after, populating the new instance's attributes. You'll override __new__ only in unusual cases (singletons, immutable subclasses); __init__ is what you write 99% of the time.

self — the instance, by convention

The first argument of every instance method is self. Python passes the instance automatically when you call obj.method(...). The name self isn't a keyword — you could call it anything — but every Python developer expects self, and any other name will get the next reader to stop and frown. Don't fight it.

Calling methods — bound vs unbound

When you access obj.method, Python returns a bound method — a callable that's already linked to the instance. obj.method(x) works because self is filled in for you. Calling Circle.area(c) directly also works — that's the unbound (more accurately: function-on-class) form, where you pass the instance explicitly. The bound form is what you write daily.

Principle: A class is a blueprint — instances are the things made from it. Class-level state lives on the class itself; instance-level state lives on each instance. Confusing these two creates the "why does my list keep growing across instances" class of bugs we'll cover in the next lesson.

Code

A minimal class·python
class Circle:
    def __init__(self, radius):
        self.radius = radius            # attribute set on the instance

    def area(self):
        return 3.14159 * self.radius ** 2

c = Circle(5)
print(c.radius)         # 5
print(c.area())         # 78.54

# self is the instance — Python fills it in for you
print(Circle.area(c))   # 78.54  (calling on the class, passing instance)
__init__ vs __new__·python
class Logger:
    def __new__(cls, *args, **kwargs):
        print("__new__: creating instance")
        instance = super().__new__(cls)     # actual construction
        return instance

    def __init__(self, name):
        print("__init__: setting up")
        self.name = name

l = Logger("main")
# __new__: creating instance
# __init__: setting up

# 99% of the time you only override __init__
Multiple instances — each with its own state·python
class Account:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self.balance = balance

    def deposit(self, amount):
        self.balance += amount
        return self.balance

a = Account("alice")
b = Account("bob", 100)

a.deposit(50)
b.deposit(25)

print(a.owner, a.balance)        # alice 50
print(b.owner, b.balance)        # bob 125    — independent state
Bound methods — the magic explained·python
class Greeter:
    def __init__(self, name):
        self.name = name

    def hi(self):
        return f"hello {self.name}"

g = Greeter("pippa")

# obj.method is a BOUND method — it remembers the instance
bound = g.hi
print(bound)                # <bound method Greeter.hi of <__main__.Greeter object at ...>>
print(bound())              # 'hello pippa'

# Plain Greeter.hi is just a function on the class
print(Greeter.hi(g))        # 'hello pippa' — pass instance explicitly

External links

Exercise

Define a class BankAccount with __init__(self, owner, opening_balance=0). Add three methods: deposit(amount), withdraw(amount) (raise ValueError if it would go below zero), and transfer_to(other_account, amount) that uses withdraw and deposit on the appropriate accounts. Test by creating two accounts, transferring between them, and printing each balance. Also try a transfer that would overdraw and confirm the exception fires.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.