Classes bundle data with behaviour and are how most libraries expose their APIs.
A class is a blueprint for objects; calling it creates an instance. __init__ initialises a new instance, and its first parameter, conventionally self, is that instance. Attributes set on self belong to each object individually.
Methods are functions defined in the class body. account.deposit(50) is shorthand for Account.deposit(account, 50) — Python passes the instance as self automatically. That is all self is: an explicit first argument, not a keyword.
Variables assigned directly in the class body are class attributes, shared by every instance. They suit constants and defaults. Never put a mutable value such as a list there and append to it per instance — every instance would share it. Create it in __init__ instead.
Python has no private keyword. A leading underscore, self._balance, signals "internal; don't touch from outside" and everyone respects it. Two underscores (__balance) trigger name mangling, which is meant to avoid clashes in subclasses, not for privacy.
Two special methods are worth adding to almost every class:
__repr__returns an unambiguous developer-facing string, shown in the REPL, debuggers and logs__str__(optional) returns a friendly string forprint()andstr(); it falls back to__repr__
Two more decorators show up constantly: @classmethod receives the class (cls) instead of an instance and is the idiomatic way to write alternative constructors like from_string; @staticmethod receives neither and is just a function namespaced in the class.
If your class is mostly fields with little behaviour, a dataclass will write __init__ and __repr__ for you.
Example
class Account:
interest_rate = 0.02 # class attribute, shared
def __init__(self, owner, balance=0):
self.owner = owner # instance attributes
self._balance = balance
def deposit(self, amount):
if amount <= 0:
raise ValueError("deposit must be positive")
self._balance += amount
@classmethod
def from_string(cls, text): # alternative constructor
owner, balance = text.split(":")
return cls(owner, int(balance))
def __repr__(self):
return f"Account({self.owner!r}, {self._balance})"
acct = Account("ana", 100)
acct.deposit(50)
print(acct) # Account('ana', 150)
print(Account.from_string("ben:20")) # Account('ben', 20)
print(acct.interest_rate) # 0.02Remember
selfis just the instance, passed automatically as the first argument- Set per-instance state in
__init__; class-body variables are shared - Always give classes a helpful
__repr__
Watch out for
- Defining a mutable class attribute like
items = []and mutating it per instance.