class Dog:
species = "Canis familiaris" # class attribute — shared by every instance
def __init__(self, name, age):
self.name = name # instance attribute — unique per instance
self.age = age
def bark(self):
return f"{self.name} says woof!"
rex = Dog("Rex", 3)
rex.bark() # "Rex says woof!"
rex.species # "Canis familiaris" — found on the class, not the instance
self is not a keyword — it's just the conventional name for the first parameter of an instance method, which Python automatically binds to the instance the method was called on (rex.bark() is sugar for Dog.bark(rex)). Nothing stops you from naming it something else, but every Python codebase you'll ever read uses self.
The exact same "shared, evaluated once" trap from the function-default section applies to mutable class attributes:
class ShoppingCart:
items = [] # DANGER — one list shared by every instance!
def add(self, item):
self.items.append(item)
cart1 = ShoppingCart()
cart2 = ShoppingCart()
cart1.add("apple")
cart2.items # ["apple"] — cart2 sees cart1's item! Same underlying list.
Fix it exactly the same way: define mutable state in __init__ so each instance gets its own:
class ShoppingCart:
def __init__(self):
self.items = [] # a fresh list per instance