11 / 14
Guide Book · Chapter 11

Classes & objects

Defining classes, instances, methods, inheritance, properties and dunder methods, and how attribute lookup really works.

A class and its instances

python
class Account:
    bank = "PyBank"                      # class attribute, shared

    def __init__(self, owner, balance=0):
        self.owner = owner               # instance attributes
        self.balance = balance

    def deposit(self, amount):
        self.balance += amount
        return self

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

a = Account("Ada").deposit(50).deposit(25)
print(a, a.bank, a.__dict__)
python
class Account:
    def __init__(self):
        self.balance = 0
    def deposit(self, amount):
        self.balance += amount

a = Account()
m = a.deposit
print(m, m.__self__ is a, m.__func__ is Account.deposit)
Account.deposit(a, 10)
print(a.balance)

Attribute lookup order

For obj.name, Python checks:

  1. data descriptors on the class (like @property)
  2. the instance's own __dict__
  3. the class, then its parents in MRO order
  4. __getattr__, if defined, as a last resort
python
class Temperature:
    def __init__(self, celsius):
        self._c = celsius

    @property
    def fahrenheit(self):              # computed on every access
        return self._c * 9 / 5 + 32

    @fahrenheit.setter
    def fahrenheit(self, value):
        self._c = (value - 32) * 5 / 9

t = Temperature(100)
print(t.fahrenheit)
t.fahrenheit = 32
print(t._c)
python
Dog = type("Dog", (), {"sound": "woof", "speak": lambda self: self.sound})
print(Dog().speak(), type(Dog), Dog.__name__)

Inheritance and super()

python
class Animal:
    def __init__(self, name):
        self.name = name
    def speak(self):
        return "..."
    def intro(self):
        return f"{self.name}: {self.speak()}"

class Dog(Animal):
    def speak(self):
        return "woof"

class Puppy(Dog):
    def __init__(self, name, age):
        super().__init__(name)
        self.age = age
    def speak(self):
        return super().speak() + " (tiny)"

p = Puppy("Rex", 1)
print(p.intro(), isinstance(p, Animal), [c.__name__ for c in Puppy.__mro__])
python
class A:
    def hi(self): return "A"
class B(A):
    def hi(self): return "B→" + super().hi()
class C(A):
    def hi(self): return "C→" + super().hi()
class D(B, C):
    def hi(self): return "D→" + super().hi()

print(D().hi(), [k.__name__ for k in D.__mro__])

Dunder methods

They hook your objects into Python's syntax:

You writePython calls
len(x)x.__len__()
x[i]x.__getitem__(i)
x == yx.__eq__(y)
x + yx.__add__(y)
for i in xx.__iter__()
str(x) / repr(x)__str__ / __repr__
with x:__enter__ / __exit__
x()x.__call__()
python
class Deck:
    def __init__(self):
        self.cards = [f"{r}{s}" for s in "♠♥" for r in "AKQ"]
    def __len__(self):
        return len(self.cards)
    def __getitem__(self, i):
        return self.cards[i]

d = Deck()
print(len(d), d[0], d[-1], "Q♥" in d, list(reversed(d))[:2])   # __getitem__ gives iteration & `in` for free

Dataclasses

python
from dataclasses import dataclass, field

@dataclass(order=True)
class Item:
    price: float
    name: str = field(compare=False)
    tags: list = field(default_factory=list, compare=False)

items = [Item(3.5, "tea"), Item(1.2, "gum")]
print(sorted(items)[0], Item(1, "a") == Item(1, "b"))
python
import sys

class P:
    def __init__(self): self.x, self.y = 1, 2
class S:
    __slots__ = ("x", "y")
    def __init__(self): self.x, self.y = 1, 2

p, s = P(), S()
print(sys.getsizeof(p) + sys.getsizeof(p.__dict__), sys.getsizeof(s), hasattr(s, "__dict__"))

🧭 Scenario: Modeling an online shop: a Cart class holding Items, with __len__ for the item count, __iter__ to loop over items, and a total property. The rest of your code can then write len(cart) and for item in cart: naturally.

main.py
Loading editor…
No output yet
Press Run or ⌘↵ to compile and run your code.

Search

Jump to a lesson or a past run