Skip to content

Functions and Objects

Defining a function

def area(width, height):
    return width * height

area(3, 4)          # positional
area(width=3, height=4)   # keyword — clearer when there are several

return hands a value back and stops the function. No return means None.

Parameters

def greet(name, greeting="Hello"):        # default value
def total(*numbers):                      # extra positionals → tuple
def config(**options):                    # extra keywords → dict
def mixed(a, b=1, *args, **kwargs):       # the full ordering

Parameters with defaults must follow those without.

Never use a mutable default

def broken(item, basket=[]):     # the SAME list on every call
    ...

def fixed(item, basket=None):    # do this instead
    if basket is None:
        basket = []

Spreading at the call site

args = [1, 2, 3]
total(*args)                # same as total(1, 2, 3)

opts = {"name": "Ada"}
greet(**opts)               # same as greet(name="Ada")

Guard clauses

def safe_average(numbers):
    if not numbers:
        return None
    return sum(numbers) / len(numbers)

Handle the awkward case first and return; the main body stays unindented.

Scope

SituationBehaviour
name created in a functionlocal, gone when it returns
reading an outer nameallowed
assigning to an outer namecreates a new local
global xrebinds the module-level name — almost always avoidable
list.append() on an outer listworks: mutating is not rebinding
count = 0
def bump():
    count = count + 1     # UnboundLocalError

Pass the value in and return the new one instead.

Lambdas and sorting

sorted(words, key=len)                          # no lambda needed
sorted(people, key=lambda p: p["age"])
sorted(people, key=lambda p: (-p["score"], p["name"]))   # two fields
sorted(values, reverse=True)

A tuple key sorts by the first element, then the second as a tie-breaker. Negating a number reverses just that field.

list(map(str.upper, words))
list(filter(None, values))       # drop falsy values

A comprehension usually reads better than map or filter.

Classes

class Dog:
    def __init__(self, name, breed):
        self.name = name
        self.breed = breed

    def speak(self):
        return f"{self.name} says woof"

rex = Dog("Rex", "collie")
rex.speak()
  • __init__ runs when an instance is created.
  • self is the instance; Python passes it for you.
  • Attributes go on self, inside __init__.

Never put a mutable value in the class body — every instance would share it:

class Bad:
    items = []          # shared by all instances

class Good:
    def __init__(self):
        self.items = []  # one per instance

Inheritance

class Animal:
    def __init__(self, name):
        self.name = name

class Cat(Animal):
    def __init__(self, name, indoor):
        super().__init__(name)     # run the parent's setup
        self.indoor = indoor

    def speak(self):               # override
        return "Meow"

isinstance(obj, Animal) is True for a Cat. Reach for inheritance when the child genuinely is a kind of the parent.

Dunder methods

MethodCalled by
__init__creating an instance
__str__print(obj), str(obj)
__repr__the console, debugging
__eq__a == b
__len__len(obj)
__lt__a < b, and sorting
__contains__x in obj

Without __eq__, two objects with identical contents are not equal — Python falls back to comparing identity.

Dataclasses

from dataclasses import dataclass, field

@dataclass
class Product:
    name: str
    price: float
    quantity: int = 1
    tags: list = field(default_factory=list)   # the safe mutable default

    def total(self):
        return self.price * self.quantity

You get __init__, __repr__ and __eq__ written for you. Fields with defaults come last, and field(default_factory=list) is how a dataclass avoids the shared-list trap.

This cheatsheet is the summary. If you want to build it yourself, the Functions and Objects course walks you through it in the browser — the first lesson is free.