← Область видимости · 🏠 Домой · Walrus-оператор →
Коротко. В определении функции *args собирает лишние позиционные
аргументы в кортеж, **kwargs — лишние именованные в словарь. В вызове те же
звёздочки работают наоборот — распаковывают последовательность и словарь
в аргументы.
def f(a, b=2, *args, key=None, **kwargs):
print(a, b, args, key, kwargs)
f(1, 2, 3, 4, key="k", extra=5)
# 1 2 (3, 4) k {'extra': 5}
args = (1, 2)
kw = {"key": "z"}
f(*args, **kw)
# 1 2 () z {}Важное следствие: всё, что идёт после *args (или после голой *), может
быть передано только по имени — это keyword-only параметры. Так делают опции,
которые не должны читаться как «третий позиционный аргумент».
def connect(host, *, timeout=5, retries=3): ...
connect("db", 10) # TypeError: takes 1 positional argument but 2 were given
connect("db", timeout=10) # okПодвох. Имена args/kwargs — только соглашение; значение имеют
звёздочки. И *args — кортеж, а не список: он неизменяем.
Глубже. Зеркальный механизм — позиционно-только параметры через /
(def f(a, b, /, c)): такие имена нельзя передать по ключу, что позволяет
переименовывать их без слома обратной совместимости. Так объявлено большинство
функций из C-модулей стандартной библиотеки.
Коротко. Результат тот же, но map/filter возвращают ленивые итераторы
и требуют готовой функции, а comprehension читается как выражение и позволяет
писать произвольное преобразование прямо на месте. В Python идиоматичен
comprehension — кроме случая, когда функция уже есть.
list(map(str.upper, ["a", "b"])) # ['A', 'B']
[s.upper() for s in ["a", "b"]] # ['A', 'B'] — то же самое
# map выигрывает, когда преобразование — готовая функция
list(map(int, ["1", "2"]))
# лямбда вместо готовой функции — признак, что нужен comprehension
list(map(lambda s: s.strip().lower(), data)) # так лучше не писатьfilter(None, iterable) — идиома «оставить только truthy-значения»
(см. Truthy and Falsy):
list(filter(None, [0, 1, "", "x", []]))
# [1, 'x']Подвох. map и filter в Python 3 — итераторы, а не списки: их можно
пройти один раз, а len() от них не берётся. Отсюда классическая ошибка —
дважды пройтись по результату map и получить пустоту на втором проходе.
Коротко. reduce сворачивает последовательность в одно значение, применяя
функцию по накоплению. В Python 3 он вынесен из builtins в functools — как
менее читаемый, чем явный цикл или специализированные sum/math.prod/any.
from functools import reduce
reduce(lambda a, b: a * b, [1, 2, 3, 4]) # 24
reduce(lambda a, b: a + b, [], 0) # 0 — initial спасает от ошибкиПодвох. Без начального значения reduce на пустой последовательности
бросает TypeError: reduce() of empty iterable with no initial value. Третий
аргумент — не «ещё один элемент», а страховка от пустого входа.
Глубже. Гвидо ван Россум предлагал выкинуть reduce вместе с lambda
ещё в «The fate of reduce() in Python 3000». В итоге его сослали в functools,
а типовые свёртки закрыли встроенными функциями: sum, min/max, any/all,
math.prod, itertools.accumulate (когда нужны промежуточные результаты).
Коротко. Он фиксирует часть аргументов и возвращает новый вызываемый объект. Это то же, что лямбда-обёртка, но без захвата переменных — значения запоминаются в момент создания, а не в момент вызова.
from functools import partial
def log(msg, *, level="INFO"):
return f"{level}: {msg}"
warn = partial(log, level="WARN")
warn("disk")
# 'WARN: disk'Именно поэтому partial — штатный обход late binding в циклах: partial(f, i)
кладёт текущее i внутрь объекта, тогда как lambda: f(i) сошлётся на
переменную (см. Область видимости).
Глубже. partial — объект, а не функция: у него есть атрибуты func,
args, keywords. Начиная с Python 3.14 partial — дескриптор метода (есть
__get__), поэтому при обращении через экземпляр класса он превращается
в bound method, а не остаётся голым partial-объектом:
from functools import partial
def f(a, b): return (a, b)
class C:
m = partial(f, "FIXED")
C().m # bound method, не просто partial-объект (начиная с 3.14)В Python 3.13 это поведение ещё переходное — обращение к такому атрибуту
через класс/экземпляр выдаёт FutureWarning: functools.partial will be a method descriptor in future Python versions. Чтобы сохранить старое
поведение (не-метод, как раньше), достаточно обернуть в staticmethod:
m = staticmethod(partial(f, "FIXED")). Для «метода с заранее фиксированным
аргументом» по-прежнему есть специализированный functools.partialmethod.