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:
- data descriptors on the class (like
@property) - the instance's own
__dict__ - the class, then its parents in MRO order
__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 write | Python calls |
|---|---|
len(x) | x.__len__() |
x[i] | x.__getitem__(i) |
x == y | x.__eq__(y) |
x + y | x.__add__(y) |
for i in x | x.__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 freeDataclasses
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.