Yesterday's pain: dicts-of-dicts have no declared shape. Python's fix is class:
class Item:
def __init__(self, name, value):
self.name = name
self.value = value
a = Item("a", 5)
print(a.name, a.value)What's self?
The instance being initialised. When you call Item("a", 5), Python creates a fresh empty object and passes it in as self. The body assigns the two arguments to attributes on that object: self.name = name. After __init__ returns, the populated object is what Item("a", 5) evaluates to.
So __init__ doesn't return the instance — it just sets it up?
Right. __init__ returns None (implicitly); the call Item(...) is what returns the instance. The class itself acts as the factory — it creates the empty object, calls __init__ on it, then hands the populated object back to you.
Why the double underscores around init?
Python's naming convention for "this name has special meaning to the language" — the so-called dunder names ("double underscore"). __init__ is one of dozens. You'll meet a few more (__repr__, __eq__) along the way. Always def __init__(self, ...) — never call it directly, always via Item(...).
class — defining a named shapeclass Item:
def __init__(self, name, value):
self.name = name
self.value = value| Piece | Purpose |
|---|---|
class Item: | declares a new type called Item |
def __init__(self, name, value): | the initialiser — runs once per instance |
self | the freshly-created instance, passed in by Python |
self.name = name | stores the argument as an attribute on the instance |
a = Item("a", 5)
b = Item("b", 12)
c = Item("c", 3)
print(a.name) # "a"
print(b.value) # 12Calling Item("a", 5) does three things:
Item object__init__(new_object, "a", 5) — assigns the attributesaself is the conventionPython doesn't enforce the name self — you could write def __init__(this, ...). But every Python codebase you'll ever read uses self. Don't deviate.
__init__ returns nothingThe initialiser sets attributes via self.x = .... It must not return a value (other than implicit None). The call expression — Item("a", 5) — returns the instance.
class Bad:
def __init__(self):
return 42 # TypeError: __init__() should return NoneEach instance has its own name and value:
a = Item("a", 5)
b = Item("b", 12)
a.value = 99
print(a.value, b.value) # 99 12 — independent# dict version
item_a = {"name": "a", "value": 5}
print(item_a["name"])
# class version
a = Item("a", 5)
print(a.name)Same data, different access. The class version: typos like a.naem raise AttributeError at the line they happen, editors autocomplete, the shape is documented in one place. Tomorrow we add behaviour (methods).
Yesterday's pain: dicts-of-dicts have no declared shape. Python's fix is class:
class Item:
def __init__(self, name, value):
self.name = name
self.value = value
a = Item("a", 5)
print(a.name, a.value)What's self?
The instance being initialised. When you call Item("a", 5), Python creates a fresh empty object and passes it in as self. The body assigns the two arguments to attributes on that object: self.name = name. After __init__ returns, the populated object is what Item("a", 5) evaluates to.
So __init__ doesn't return the instance — it just sets it up?
Right. __init__ returns None (implicitly); the call Item(...) is what returns the instance. The class itself acts as the factory — it creates the empty object, calls __init__ on it, then hands the populated object back to you.
Why the double underscores around init?
Python's naming convention for "this name has special meaning to the language" — the so-called dunder names ("double underscore"). __init__ is one of dozens. You'll meet a few more (__repr__, __eq__) along the way. Always def __init__(self, ...) — never call it directly, always via Item(...).
class — defining a named shapeclass Item:
def __init__(self, name, value):
self.name = name
self.value = value| Piece | Purpose |
|---|---|
class Item: | declares a new type called Item |
def __init__(self, name, value): | the initialiser — runs once per instance |
self | the freshly-created instance, passed in by Python |
self.name = name | stores the argument as an attribute on the instance |
a = Item("a", 5)
b = Item("b", 12)
c = Item("c", 3)
print(a.name) # "a"
print(b.value) # 12Calling Item("a", 5) does three things:
Item object__init__(new_object, "a", 5) — assigns the attributesaself is the conventionPython doesn't enforce the name self — you could write def __init__(this, ...). But every Python codebase you'll ever read uses self. Don't deviate.
__init__ returns nothingThe initialiser sets attributes via self.x = .... It must not return a value (other than implicit None). The call expression — Item("a", 5) — returns the instance.
class Bad:
def __init__(self):
return 42 # TypeError: __init__() should return NoneEach instance has its own name and value:
a = Item("a", 5)
b = Item("b", 12)
a.value = 99
print(a.value, b.value) # 99 12 — independent# dict version
item_a = {"name": "a", "value": 5}
print(item_a["name"])
# class version
a = Item("a", 5)
print(a.name)Same data, different access. The class version: typos like a.naem raise AttributeError at the line they happen, editors autocomplete, the shape is documented in one place. Tomorrow we add behaviour (methods).
Create a free account to get started. Paid plans unlock all tracks.