PythonMastery
reference 6 min read · lesson 21 of 25 in Reference

Python Functions Cheat Sheet

1 · The lesson

read

A function is a named block of code that you can run again and again. You give it values, it does its work, and it can send a result back with return. This page is a short reference with examples you can run. For the full tutorials, see Functions, Lambda and Decorators.

1. def and return

def names the function. return sends a value back to the code that called the function. A function with no return line sends back None.

python
def total(price, qty):
    return price * qty

print(total(2.5, 4))        # 10.0

def greet(name):
    print("Hello", name)    # prints, but returns nothing

result = greet("Ada")       # Hello Ada
print(result)               # None

2. Parameters and arguments

A parameter is a name in the def line (price, rate). An argument is the value you pass in the call (10, 0.1). You can pass an argument by position (in the same order as the parameters) or by keyword (name=value, in any order). A parameter with = has a default value: if you leave that argument out, Python uses the default.

python
def with_tax(price, rate=0.2):
    return round(price * (1 + rate), 2)

print(with_tax(10))                  # 12.0   rate uses its default
print(with_tax(10, 0.1))             # 11.0   passed by position
print(with_tax(rate=0.5, price=10))  # 15.0   passed by keyword, in any order

with_tax() raises TypeError: price has no default, so you must pass it.

3. *args and **kwargs

A * before a parameter collects all the extra arguments passed by position into a tuple. A ** collects all the extra name=value arguments into a dict. The names args and kwargs are only a habit: *nums works the same.

python
def show(*args, **kwargs):
    return args, kwargs

print(show(1, 2))                # ((1, 2), {})
print(show(size="L", qty=3))     # ((), {'size': 'L', 'qty': 3})

def add_all(*nums):              # takes any number of values
    return sum(nums)

print(add_all(4, 5, 6))          # 15
print(add_all())                 # 0
+ setup added so this can run · defines args, kwargs, nums
# Lightweight mock for objects whose attributes/methods aren't critical
class _AutoMock:
    def __init__(self, name='mock'): self._name = name
    def __getattr__(self, k): return _AutoMock(self._name + '.' + k)
    def __call__(self, *a, **kw):
        print('-> ' + self._name + '() called')
        return _AutoMock(self._name + '()')
    def __repr__(self): return '<mock ' + self._name + '>'
    def __str__(self): return '<mock ' + self._name + '>'
    def __bool__(self): return True
    def __iter__(self): return iter([])
    def __len__(self): return 0
    def __getitem__(self, k): return _AutoMock(self._name + '[...]')
    def __setitem__(self, k, v): pass
    def __enter__(self): return self
    def __exit__(self, *a): return False
    async def __aenter__(self): return self
    async def __aexit__(self, *a): return False
    def __add__(self, o): return self
    def __radd__(self, o): return self
    def __sub__(self, o): return self
    def __mul__(self, o): return self
    def __rmul__(self, o): return self
    def __truediv__(self, o): return self
    def __eq__(self, o): return isinstance(o, _AutoMock)
    def __hash__(self): return hash(self._name)
    def __lt__(self, o): return True
    def __le__(self, o): return True
    def __gt__(self, o): return False
    def __ge__(self, o): return False
    def __mro_entries__(self, bases): return (object,)

args = _AutoMock('args')
kwargs = _AutoMock('kwargs')
nums = _AutoMock('nums')

4. * and ** in a call: unpack

In a call, * does the opposite: it spreads a list into separate arguments. ** spreads a dict into name=value arguments.

python
order = [2.5, 4]
print(total(*order))        # 10.0   same as total(2.5, 4)

order = {"price": 3, "qty": 2}
print(total(**order))       # 6      same as total(price=3, qty=2)
+ setup added so this can run · re-runs 1 earlier block on this page first
# Lightweight mock for objects whose attributes/methods aren't critical
class _AutoMock:
    def __init__(self, name='mock'): self._name = name
    def __getattr__(self, k): return _AutoMock(self._name + '.' + k)
    def __call__(self, *a, **kw):
        print('-> ' + self._name + '() called')
        return _AutoMock(self._name + '()')
    def __repr__(self): return '<mock ' + self._name + '>'
    def __str__(self): return '<mock ' + self._name + '>'
    def __bool__(self): return True
    def __iter__(self): return iter([])
    def __len__(self): return 0
    def __getitem__(self, k): return _AutoMock(self._name + '[...]')
    def __setitem__(self, k, v): pass
    def __enter__(self): return self
    def __exit__(self, *a): return False
    async def __aenter__(self): return self
    async def __aexit__(self, *a): return False
    def __add__(self, o): return self
    def __radd__(self, o): return self
    def __sub__(self, o): return self
    def __mul__(self, o): return self
    def __rmul__(self, o): return self
    def __truediv__(self, o): return self
    def __eq__(self, o): return isinstance(o, _AutoMock)
    def __hash__(self): return hash(self._name)
    def __lt__(self, o): return True
    def __le__(self, o): return True
    def __gt__(self, o): return False
    def __ge__(self, o): return False
    def __mro_entries__(self, bases): return (object,)

def total(*_a, **_kw):
    print('-> total() called')
    return _AutoMock('total()')

# Earlier blocks on this page that this one builds on, re-run quietly first.
_pm_earlier = [
    'def total(price, qty):\n    return price * qty\n\nprint(total(2.5, 4))        # 10.0\n\ndef greet(name):\n    print("Hello", name)    # prints, but returns nothing\n\nresult = greet("Ada")       # Hello Ada\nprint(result)               # None',
]
def _pm_run_earlier(_srcs, _g):
    import sys as _s, io as _io, warnings as _w
    for _src in _srcs:
        _o, _e = _s.stdout, _s.stderr
        _s.stdout = _s.stderr = _io.StringIO()
        try:
            with _w.catch_warnings():
                _w.simplefilter('ignore')
                exec(compile(_src, '<earlier block>', 'exec'), _g)
        except (Exception, SystemExit):
            pass
        finally:
            _s.stdout, _s.stderr = _o, _e
    if 'matplotlib.pyplot' in _s.modules:
        _s.modules['matplotlib.pyplot'].close('all')   # their plots belong to their own blocks
_pm_run_earlier(_pm_earlier, globals())
del _pm_earlier, _pm_run_earlier

5. / and * in the def line

Names before / must be passed by position. Names after * must be passed by keyword. You will see / and * in library functions more often than you will write them yourself.

python
def move(x, y, /, *, speed=1):
    return x, y, speed

print(move(1, 2, speed=5))     # (1, 2, 5)
+ setup added so this can run · defines x, y
# Lightweight mock for objects whose attributes/methods aren't critical
class _AutoMock:
    def __init__(self, name='mock'): self._name = name
    def __getattr__(self, k): return _AutoMock(self._name + '.' + k)
    def __call__(self, *a, **kw):
        print('-> ' + self._name + '() called')
        return _AutoMock(self._name + '()')
    def __repr__(self): return '<mock ' + self._name + '>'
    def __str__(self): return '<mock ' + self._name + '>'
    def __bool__(self): return True
    def __iter__(self): return iter([])
    def __len__(self): return 0
    def __getitem__(self, k): return _AutoMock(self._name + '[...]')
    def __setitem__(self, k, v): pass
    def __enter__(self): return self
    def __exit__(self, *a): return False
    async def __aenter__(self): return self
    async def __aexit__(self, *a): return False
    def __add__(self, o): return self
    def __radd__(self, o): return self
    def __sub__(self, o): return self
    def __mul__(self, o): return self
    def __rmul__(self, o): return self
    def __truediv__(self, o): return self
    def __eq__(self, o): return isinstance(o, _AutoMock)
    def __hash__(self): return hash(self._name)
    def __lt__(self, o): return True
    def __le__(self, o): return True
    def __gt__(self, o): return False
    def __ge__(self, o): return False
    def __mro_entries__(self, bases): return (object,)

x = _AutoMock('x')
y = _AutoMock('y')

move(1, 2, 5) raises TypeError because speed must be named. move(x=1, y=2) raises TypeError because x and y must not be named.

6. Return several values

return a, b sends back one tuple that holds two values. You can unpack the tuple into two names: each name gets one value.

python
def min_max(scores):
    return min(scores), max(scores)

print(min_max([70, 92, 81]))         # (70, 92)
low, high = min_max([70, 92, 81])
print(high)                          # 92

7. Scope: where a name can be used

A name you set inside a function is local: it is a new name that exists only inside that function. It does not change a name with the same spelling outside.

python
score = 10

def reset():
    score = 0        # a new local name

reset()
print(score)         # 10

global lets a function change a name from outside. Use it rarely: returning the new value is clearer. nonlocal lets an inner function change a name that belongs to the function around it. Below, step() changes n, which belongs to counter(), so n is kept between calls.

python
def add_point():
    global score
    score += 1

add_point()
print(score)         # 11

def counter():
    n = 0
    def step():
        nonlocal n
        n += 1
        return n
    return step

step = counter()
print(step(), step())   # 1 2
+ setup added so this can run · re-runs 1 earlier block on this page first
score = 1

# Earlier blocks on this page that this one builds on, re-run quietly first.
_pm_earlier = [
    'score = 10\n\ndef reset():\n    score = 0        # a new local name\n\nreset()\nprint(score)         # 10',
]
def _pm_run_earlier(_srcs, _g):
    import sys as _s, io as _io, warnings as _w
    for _src in _srcs:
        _o, _e = _s.stdout, _s.stderr
        _s.stdout = _s.stderr = _io.StringIO()
        try:
            with _w.catch_warnings():
                _w.simplefilter('ignore')
                exec(compile(_src, '<earlier block>', 'exec'), _g)
        except (Exception, SystemExit):
            pass
        finally:
            _s.stdout, _s.stderr = _o, _e
    if 'matplotlib.pyplot' in _s.modules:
        _s.modules['matplotlib.pyplot'].close('all')   # their plots belong to their own blocks
_pm_run_earlier(_pm_earlier, globals())
del _pm_earlier, _pm_run_earlier

8. lambda and functions as values

A function is a value, like a number or a list. You can pass it to another function, such as sorted() or map(). Pass the function itself, without (). lambda makes a small function with no name, written in one expression.

python
people = [("Ada", 36), ("Linus", 28), ("Grace", 45)]

# lambda p: p[1] takes p and returns p[1], the age
print(sorted(people, key=lambda p: p[1]))
# [('Linus', 28), ('Ada', 36), ('Grace', 45)]

print(list(map(str.upper, ["ada", "hedy"])))   # ['ADA', 'HEDY']

size = len                 # another name for the same function
print(size("Grace"))       # 5

9. Docstrings and type hints

The string on the first line of a function is its docstring: help() shows it. Type hints say which types you expect. Python does not check them when the code runs; tools such as mypy and your editor do.

python
def area(w: float, h: float) -> float:
    """Area of a w by h rectangle."""
    return w * h

print(area.__doc__)      # Area of a w by h rectangle.
print(area(2, 3))        # 6
print(area("ab", 3))     # ababab

10. A tiny decorator

A decorator takes a function and returns a new function. The new function calls the old one and does some extra work. @shout above a def is the same as writing hello = shout(hello) after it.

python
def shout(func):
    def wrapper(name):
        return func(name).upper()
    return wrapper

@shout
def hello(name):
    return f"hello {name}"

print(hello("Hedy"))     # HELLO HEDY

Common mistakes

A list as a default value. The default list is made once, when Python reads the def line, and every call shares it:

python
def add(item, cart=[]):
    cart.append(item)
    return cart

print(add("tea"))        # ['tea']
print(add("pie"))        # ['tea', 'pie']

def add(item, cart=None):      # the fix
    if cart is None:
        cart = []
    cart.append(item)
    return cart

print(add("tea"))        # ['tea']
print(add("pie"))        # ['pie']

Forgetting return. The function calculates the value and then loses it, so the call gives back None:

python
def double(n):
    n * 2

print(double(4))         # None

print instead of return. print only shows a value on the screen. If the caller needs the value, use return. With def total(a, b): print(a + b), the line total(1, 2) + 1 raises TypeError, because total gives back None.

Calling instead of passing. sorted(names, key=len()) raises TypeError. Pass the function, key=len. len is the function; len() calls it.

Using a built-in name. After list = ["tea"], the line list("ab") raises TypeError: 'list' object is not callable. Choose another name, such as items.

Found this useful? Share it with a friend who’s learning Python.