class
class builds a new type. Attributes set on self belong to one instance; attributes written in the class body are shared by all of them.
class Name: attr = value def method(self): ...
class Name: def __init__(self, x): self.x = x
class Child(Parent): def __init__(self, x): super().__init__(x)
@dataclass class Point: x: int y: int = 0
Demo
class Cart: shared = [] def __init__(self): self.own = [] def add(self, item): self.shared.append(item) self.own.append(item) a, b = Cart(), Cart() for item in ['apple', 'pear']: a.add(item) (a.own, b.own, b.shared)
In the class vs instance tab, b never called add(), yet b.shared holds every item: shared lives on the class, so a.shared and b.shared are the same list. In the attribute lookup tab, sound is found on Dog before Animal is even checked (overriding), legs falls through to Animal, and __init__ comes all the way from object. The trace is a simplified model of what rex.attr does: real lookup also gives data descriptors such as properties on the class priority over the instance dict.
Syntax slots
| Name | Type | Required | Description |
|---|---|---|---|
| Name | name | yes | The name the new class is bound to. By convention CapWords. |
| Base | expression list | no | Classes to inherit from, comma-separated. Omitted means object. Keyword arguments such as metaclass=... also go here. |
| body | block | yes | Runs once, when the class statement executes. Names it assigns (attributes, def methods) become the class namespace. |
| self | parameter | yes | First parameter of every normal method: the instance the method was called on. The name self is a convention, not a keyword. |
Common patterns
class Temp: def __init__(self, celsius): self.celsius = celsius @classmethod def from_fahrenheit(cls, f): return cls((f - 32) * 5 / 9)
class Rect: def __init__(self, w, h): self.w, self.h = w, h @property def area(self): return self.w * self.h
from dataclasses import dataclass, field @dataclass class Order: id: int items: list = field(default_factory=list)
class Money: def __init__(self, cents): self.cents = cents def __repr__(self): return f'Money({self.cents!r})'
Examples
Pitfalls
class Team: members = [] def join(self, name): self.members.append(name) a, b = Team(), Team() a.join('Ann') b.members
class Team: def __init__(self): self.members = [] def join(self, name): self.members.append(name) a, b = Team(), Team() a.join('Ann') b.members
class Greeter: def hello(): return 'hi' Greeter().hello()
class Greeter: def hello(self): return 'hi' Greeter().hello()
class Counter: count = 0 def __init__(self): self.count += 1 Counter() Counter() Counter.count
class Counter: count = 0 def __init__(self): type(self).count += 1 Counter() Counter() Counter.count
class Animal: def __init__(self, name): self.name = name class Dog(Animal): def __init__(self, name): self.tricks = [] Dog('Rex').name
class Animal: def __init__(self, name): self.name = name class Dog(Animal): def __init__(self, name): super().__init__(name) self.tricks = [] Dog('Rex').name
When to use
- Data and the operations on it belong together (a Cart with add() and total())
- You need several objects with the same shape and behaviour
- You want your own type to work with len(), ==, for, with and friends (dunder methods)
- Just a record of fields → @dataclass (still a class, far less code) or a NamedTuple
- A class with one method and __init__ → usually a plain function
- A bag of constants or stateless helpers → a module
Notes
FAQ
The instance the method was called on. obj.method(1) is effectively Class.method(obj, 1), so the first parameter receives obj. It is named self by convention only — any name works, but every normal method needs that first parameter.