Как передаются аргументы
Ни «по значению», ни «по ссылке» в классическом смысле: функция получает ссылку на тот же объект (call by sharing).
def add(items):
items.append(1) # меняет объект — видно снаружи
items = [99] # переприсваивает имя — снаружи не видно
Если менять аргумент не нужно — копируйте или возвращайте новый объект.
*args и **kwargs
*args собирает лишние позиционные аргументы в кортеж, **kwargs — лишние именованные в словарь. При вызове звёздочки распаковывают: f(*values, **options).
def connect(host, /, port=5432, *, timeout=5): ...
До / — только позиционные, после * — только именованные.
Области видимости — LEGB
Имя ищется по цепочке: Local → Enclosing → Global → Built-in. Присваивание внутри функции делает имя локальным для всей функции — отсюда UnboundLocalError, если прочитать его до присваивания. Писать во внешнюю переменную — через global или nonlocal. Циклы и if своей области не создают.
Изменяемое значение по умолчанию
Значение по умолчанию вычисляется один раз — при определении функции:
def add_item(item, items=[]): # общий список на все вызовы
items.append(item)
return items
def add_item(item, items=None): # правильно
if items is None:
items = []
items.append(item)
return items
Замыкания
Вложенная функция помнит переменные внешней после её завершения:
def counter():
n = 0
def inc():
nonlocal n
n += 1
return n
return inc
Захватывается переменная, а не значение — оно читается в момент вызова. Отсюда ловушка:
fs = [lambda: i for i in range(3)]
[f() for f in fs] # [2, 2, 2]
fs = [lambda i=i: i for i in range(3)] # [0, 1, 2]
Декораторы
Декоратор — функция, которая принимает функцию и возвращает новую; @dec — сахар для f = dec(f).
import functools, time
def timed(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
try:
return func(*args, **kwargs)
finally:
print(func.__name__, time.perf_counter() - start)
return wrapper
functools.wraps копирует в обёртку имя, docstring и ставит __wrapped__ — без него логи, документация и pytest видят «wrapper».
Декоратор с параметрами — фабрика декораторов, три уровня вложенности: @retry(times=3) равно f = retry(times=3)(f). Для async-функций обёртка тоже должна быть async.
functools
| Что | Зачем |
|---|---|
lru_cache(maxsize), cache | мемоизация; аргументы должны быть хешируемыми |
partial | зафиксировать часть аргументов |
singledispatch | перегрузка по типу первого аргумента |
cached_property | вычислить атрибут один раз |
lru_cache на методе держит ссылку на self — объекты не освобождаются.
Что запомнить
- Функция получает ссылку на объект: изменить можно, переприсвоить снаружи — нет.
- Изменяемое значение по умолчанию —
Noneи создание внутри. - Замыкание видит переменную в момент вызова.
- Обёртку декоратора всегда помечают
functools.wraps.