Classes and instances

Python · Practical · 12 min

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 for print() and str(); 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.02

Remember

  • self is 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.

Check yourself

  1. `acct.deposit(50)` is equivalent to which call?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/tutorial/classes.html