PythonMastery
reference 6 min read · lesson 20 of 25 in Reference

Python Classes Cheat Sheet

1 · The lesson

read

A class is a plan for a kind of thing: what data it holds and what it can do. An object is one real thing made from that plan. This page is a short reference with examples you can run. For the full tutorials, see Object-oriented programming, Inheritance and Dataclasses.

1. A class and an object

__init__ runs once, when you make an object. It sets the object's starting data. A method is a function that belongs to the class. An attribute is a piece of data stored on the object.

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

acc = Account("Ada", 100)      # call the class to make an object
print(acc.owner)               # Ada
print(acc.deposit(50))         # 150
print(acc.balance)             # 150

grace = Account("Grace")       # each object has its own data
print(grace.balance)           # 0

2. self: the object itself

Inside a method, self is the object the method was called on. self.balance means "this object's balance". Every method takes self first, but you never pass it yourself: Python does it for you.

python
acc = Account("Ada", 100)
acc.deposit(50)                 # what you write
Account.deposit(acc, 50)        # what Python runs: acc goes in as self
print(acc.balance)              # 200
+ setup added so this can run · re-runs 1 earlier block on this page first
# Lightweight mock for objects whose attributes/methods aren't critical
class _AutoMock:
    def __init__(self, name='mock'): self._name = name
    def __getattr__(self, k): return _AutoMock(self._name + '.' + k)
    def __call__(self, *a, **kw):
        print('-> ' + self._name + '() called')
        return _AutoMock(self._name + '()')
    def __repr__(self): return '<mock ' + self._name + '>'
    def __str__(self): return '<mock ' + self._name + '>'
    def __bool__(self): return True
    def __iter__(self): return iter([])
    def __len__(self): return 0
    def __getitem__(self, k): return _AutoMock(self._name + '[...]')
    def __setitem__(self, k, v): pass
    def __enter__(self): return self
    def __exit__(self, *a): return False
    async def __aenter__(self): return self
    async def __aexit__(self, *a): return False
    def __add__(self, o): return self
    def __radd__(self, o): return self
    def __sub__(self, o): return self
    def __mul__(self, o): return self
    def __rmul__(self, o): return self
    def __truediv__(self, o): return self
    def __eq__(self, o): return isinstance(o, _AutoMock)
    def __hash__(self): return hash(self._name)
    def __lt__(self, o): return True
    def __le__(self, o): return True
    def __gt__(self, o): return False
    def __ge__(self, o): return False
    def __mro_entries__(self, bases): return (object,)

Account = _AutoMock('Account')

# Earlier blocks on this page that this one builds on, re-run quietly first.
_pm_earlier = [
    'class Account:\n    def __init__(self, owner, balance=0):\n        self.owner = owner\n        self.balance = balance\n\n    def deposit(self, amount):\n        self.balance += amount\n        return self.balance\n\nacc = Account("Ada", 100)      # call the class to make an object\nprint(acc.owner)               # Ada\nprint(acc.deposit(50))         # 150\nprint(acc.balance)             # 150\n\ngrace = Account("Grace")       # each object has its own data\nprint(grace.balance)           # 0',
]
def _pm_run_earlier(_srcs, _g):
    import sys as _s, io as _io, warnings as _w
    for _src in _srcs:
        _o, _e = _s.stdout, _s.stderr
        _s.stdout = _s.stderr = _io.StringIO()
        try:
            with _w.catch_warnings():
                _w.simplefilter('ignore')
                exec(compile(_src, '<earlier block>', 'exec'), _g)
        except (Exception, SystemExit):
            pass
        finally:
            _s.stdout, _s.stderr = _o, _e
    if 'matplotlib.pyplot' in _s.modules:
        _s.modules['matplotlib.pyplot'].close('all')   # their plots belong to their own blocks
_pm_run_earlier(_pm_earlier, globals())
del _pm_earlier, _pm_run_earlier

3. Printing an object: __repr__ and __str__

Without __repr__ and __str__, printing an object shows something like <__main__.Account object at 0x7f...>. __repr__ is for programmers: the >>> prompt and lists use it. __str__ is for people: print() and f-strings use it.

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

    def __repr__(self):
        return f"Account({self.owner!r})"

    def __str__(self):
        return f"Owner: {self.owner}"

acc = Account("Ada", 100)
print(acc)          # Owner: Ada
print([acc])        # [Account('Ada')]
print(repr(acc))    # Account('Ada')

4. Class attributes and instance attributes

An attribute set in the class body is a class attribute: all objects share it. An attribute set on self is an instance attribute: each object has its own.

python
class Branch:
    bank = "Northwind"          # class attribute: shared by all

    def __init__(self, city):
        self.city = city        # instance attribute: one per object

a, b = Branch("Oslo"), Branch("Lima")
print(a.bank, b.bank)           # Northwind Northwind
print(a.city, b.city)           # Oslo Lima

Branch.bank = "Eastgate"        # change bank on the class
print(b.bank)                   # Eastgate

5. @property

A property is a method that you read like an attribute, without (). The value is calculated again every time you read it.

python
class Temp:
    def __init__(self, celsius):
        self.celsius = celsius

    @property
    def fahrenheit(self):
        return self.celsius * 9 / 5 + 32

t = Temp(20)
print(t.fahrenheit)     # 68.0
t.celsius = 30
print(t.fahrenheit)     # 86.0

t.fahrenheit = 50 raises AttributeError: a property is read-only unless you also write a setter with @fahrenheit.setter.

6. @classmethod and @staticmethod

A class method gets the class as cls, not an object. Use it for another way to make an object. A static method gets no self and no cls: it is a plain function that you keep in the class because it is related to the class.

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

    @classmethod
    def from_text(cls, text):
        owner, balance = text.split(",")
        return cls(owner, int(balance))

    @staticmethod
    def is_valid(amount):
        return amount > 0

hedy = Account.from_text("Hedy,250")
print(hedy.owner, hedy.balance)     # Hedy 250
print(Account.is_valid(-5))         # False

7. Inheritance and super()

A child class (here Savings) is built from a parent class (here Account) and gets all the methods of the parent class. super().__init__(...) runs the parent's __init__, so the child does not have to repeat it.

python
class Savings(Account):
    def __init__(self, owner, rate):
        super().__init__(owner)        # sets owner and balance
        self.rate = rate

    def add_interest(self):
        gain = self.balance * self.rate
        return self.deposit(gain)      # deposit() comes from Account

s = Savings("Margaret", 0.02)
s.deposit(1000)
print(s.add_interest())               # 1020.0
print(isinstance(s, Account))         # True
print(isinstance(hedy, Savings))      # False
+ setup added so this can run · re-runs 4 earlier blocks on this page first
# Lightweight mock for objects whose attributes/methods aren't critical
class _AutoMock:
    def __init__(self, name='mock'): self._name = name
    def __getattr__(self, k): return _AutoMock(self._name + '.' + k)
    def __call__(self, *a, **kw):
        print('-> ' + self._name + '() called')
        return _AutoMock(self._name + '()')
    def __repr__(self): return '<mock ' + self._name + '>'
    def __str__(self): return '<mock ' + self._name + '>'
    def __bool__(self): return True
    def __iter__(self): return iter([])
    def __len__(self): return 0
    def __getitem__(self, k): return _AutoMock(self._name + '[...]')
    def __setitem__(self, k, v): pass
    def __enter__(self): return self
    def __exit__(self, *a): return False
    async def __aenter__(self): return self
    async def __aexit__(self, *a): return False
    def __add__(self, o): return self
    def __radd__(self, o): return self
    def __sub__(self, o): return self
    def __mul__(self, o): return self
    def __rmul__(self, o): return self
    def __truediv__(self, o): return self
    def __eq__(self, o): return isinstance(o, _AutoMock)
    def __hash__(self): return hash(self._name)
    def __lt__(self, o): return True
    def __le__(self, o): return True
    def __gt__(self, o): return False
    def __ge__(self, o): return False
    def __mro_entries__(self, bases): return (object,)

Account = _AutoMock('Account')
hedy = _AutoMock('hedy')

# Earlier blocks on this page that this one builds on, re-run quietly first.
_pm_earlier = [
    'class Account:\n    def __init__(self, owner, balance=0):\n        self.owner = owner\n        self.balance = balance\n\n    def deposit(self, amount):\n        self.balance += amount\n        return self.balance\n\nacc = Account("Ada", 100)      # call the class to make an object\nprint(acc.owner)               # Ada\nprint(acc.deposit(50))         # 150\nprint(acc.balance)             # 150\n\ngrace = Account("Grace")       # each object has its own data\nprint(grace.balance)           # 0',
    'acc = Account("Ada", 100)\nacc.deposit(50)                 # what you write\nAccount.deposit(acc, 50)        # what Python runs: acc goes in as self\nprint(acc.balance)              # 200',
    'class Account:\n    def __init__(self, owner, balance=0):\n        self.owner = owner\n        self.balance = balance\n\n    def deposit(self, amount):\n        self.balance += amount\n        return self.balance\n\n    def __repr__(self):\n        return f"Account({self.owner!r})"\n\n    def __str__(self):\n        return f"Owner: {self.owner}"\n\nacc = Account("Ada", 100)\nprint(acc)          # Owner: Ada\nprint([acc])        # [Account(\'Ada\')]\nprint(repr(acc))    # Account(\'Ada\')',
    'class Account:\n    def __init__(self, owner, balance=0):\n        self.owner = owner\n        self.balance = balance\n\n    def deposit(self, amount):\n        self.balance += amount\n        return self.balance\n\n    @classmethod\n    def from_text(cls, text):\n        owner, balance = text.split(",")\n        return cls(owner, int(balance))\n\n    @staticmethod\n    def is_valid(amount):\n        return amount > 0\n\nhedy = Account.from_text("Hedy,250")\nprint(hedy.owner, hedy.balance)     # Hedy 250\nprint(Account.is_valid(-5))         # False',
]
def _pm_run_earlier(_srcs, _g):
    import sys as _s, io as _io, warnings as _w
    for _src in _srcs:
        _o, _e = _s.stdout, _s.stderr
        _s.stdout = _s.stderr = _io.StringIO()
        try:
            with _w.catch_warnings():
                _w.simplefilter('ignore')
                exec(compile(_src, '<earlier block>', 'exec'), _g)
        except (Exception, SystemExit):
            pass
        finally:
            _s.stdout, _s.stderr = _o, _e
    if 'matplotlib.pyplot' in _s.modules:
        _s.modules['matplotlib.pyplot'].close('all')   # their plots belong to their own blocks
_pm_run_earlier(_pm_earlier, globals())
del _pm_earlier, _pm_run_earlier

8. @dataclass: the short way

For a class that mostly holds data, @dataclass writes __init__, __repr__ and __eq__ for you from the fields you list.

python
from dataclasses import dataclass

@dataclass
class Order:
    item: str
    qty: int
    price: float = 0.0      # a field with a default comes after those without

print(Order("tea", 2, 1.5))                  # Order(item='tea', qty=2, price=1.5)
print(Order("tea", 2) == Order("tea", 2))    # True
print(Order("pie", 1).price)                 # 0.0

9. Special methods

Methods with two underscores on each side let your objects work with Python's own operators and functions.

MethodUsed by
__len__len(obj)
__eq__a == b
__lt__a < b, and so sorted(), min(), max()
__repr__, __str__the >>> prompt, print()
python
class Playlist:
    def __init__(self, name, songs):
        self.name = name
        self.songs = songs          # the number of songs

    def __len__(self):
        return self.songs

    def __eq__(self, other):
        return self.songs == other.songs

    def __lt__(self, other):
        return self.songs < other.songs

rock, jazz = Playlist("rock", 3), Playlist("jazz", 2)
print(len(rock))                                 # 3
print(rock == Playlist("other", 3))              # True
print([p.name for p in sorted([rock, jazz])])    # ['jazz', 'rock']

Common mistakes

Forgetting self. def deposit(amount): inside a class looks fine, but acc.deposit(5) raises TypeError: ... takes 1 positional argument but 2 were given, because Python passes the object as well. Write def deposit(self, amount):.

A list as a class attribute. The list is made once and shared by every object:

python
class Team:
    members = []                # one list for all teams

a, b = Team(), Team()
a.members.append("Ada")
print(b.members)                # ['Ada']

class Team:
    def __init__(self):
        self.members = []       # fix: a new list for each object

Forgetting the (). acc.deposit gives you the method itself and runs nothing. Write acc.deposit(50).

Returning a value from __init__. __init__ only sets up the object. If it returns anything other than None, Python raises TypeError: __init__() should return None.

Comparing objects without __eq__. Without __eq__, == asks "is this the same object?":

python
class Point:
    def __init__(self, x):
        self.x = x

print(Point(1) == Point(1))     # False

Add an __eq__ method, or use @dataclass, which writes one for you.

Found this useful? Share it with a friend who’s learning Python.