Blog

__init__ and self in Python

Two things about __init__ and self surprise people who came from another language, and both are easier once you see what Python is actually doing.

self is a parameter, not a keyword

When you call d.speak(), Python calls Dog.speak(d). The instance is passed as the first argument, and self is just the name we give it.

# self is a parameter name, not a keyword
class Dog:
    def __init__(self, name):
        self.name = name
    def speak(self):
        return f"{self.name} says woof"

d = Dog('Rex')
print(d.speak())
print(Dog.speak(d))          # the same call, written out

It prints:

Rex says woof
Rex says woof

Both lines are the same call. d.speak() is shorthand.

You can prove self is only a convention:

# proof that self is only a convention
class Cat:
    def __init__(whatever, name):
        whatever.name = name
    def speak(this):
        return f"{this.name} says meow"

print(Cat('Mia').speak())

It prints:

Mia says meow

That works. Never write it — every Python reader expects self, and breaking that costs more than it could possibly gain. But knowing it is a parameter explains why you have to write it in every method signature, which otherwise looks like noise.

__init__ does not create the object

The name suggests a constructor. It is not one. By the time __init__ runs, the object already exists — __init__ only fills it in.

# __init__ does not create the object, it fills one in
class Tracked:
    def __new__(cls, *args):
        print('  __new__ ran, object does not exist yet')
        return super().__new__(cls)
    def __init__(self, value):
        print('  __init__ ran, self already exists:', type(self).__name__)
        self.value = value

t = Tracked(5)
print('value =', t.value)

It prints:

  __new__ ran, object does not exist yet
  __init__ ran, self already exists: Tracked
value = 5

__new__ is the actual constructor. You will almost never write one — it matters for immutable types and metaclasses, and nowhere else. But it explains the naming: __init__ initialises, it does not create.

Class attributes are shared

An attribute assigned in the class body belongs to the class. One exists, no matter how many instances you make.

# instance attributes vs class attributes
class Counter:
    total = 0                      # shared by every instance
    def __init__(self):
        self.count = 0             # one per instance
    def tick(self):
        self.count += 1
        Counter.total += 1

a, b = Counter(), Counter()
a.tick(); a.tick(); b.tick()
print('a.count =', a.count, '| b.count =', b.count, '| Counter.total =', Counter.total)

It prints:

a.count = 2 | b.count = 1 | Counter.total = 3

count is per instance. total is one number for the whole class.

The part that trips people

Reading a class attribute through an instance works. Assigning through an instance does not update the class — it creates a new instance attribute that shadows it.

# assigning through the instance shadows the class attribute
a.total = 99
print('a.total =', a.total, '| b.total =', b.total, '| Counter.total =', Counter.total)
print('a has its own:', 'total' in a.__dict__, '| b does not:', 'total' in b.__dict__)

It prints:

a.total = 99 | b.total = 3 | Counter.total = 3
a has its own: True | b does not: False

a now has its own total. b and the class still share the original. This is why Counter.total += 1 in tick is written against the class and not self.total += 1 — the latter would read the class value, add one, and quietly store the result on the instance.

The same rule is why a mutable class attribute is a trap. A list in the class body is shared by every instance, and self.items.append(x) mutates the shared list rather than shadowing it. Assign mutable attributes in __init__.

What to remember

  • d.speak() is Dog.speak(d). self is the first parameter, named by convention.

  • __init__ initialises an object that already exists. __new__ creates it, and you will rarely write one.

  • A class attribute is shared. Reading through an instance finds it; assigning through an instance shadows it.

  • Mutable class attributes are shared and mutable — put them in __init__.

How useful was this post?

Click on a heart to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.