← Функциональный стиль · 🏠 Домой · Декораторы →
Коротко. := (PEP 572, Python 3.8+) — присваивание как выражение:
оно и запоминает значение в переменной, и сразу возвращает его. Нужен там,
где иначе пришлось бы либо вычислять одно и то же дважды, либо выносить
присваивание строкой выше.
# python 3.8+
data = list(range(12))
if (n := len(data)) > 10:
print(f"слишком много элементов: {n}")
# слишком много элементов: 12Три канонических применения:
# 1. результат регулярки нужен и в условии, и в теле
if (m := re.match(r"(\w+)=(\w+)", line)):
key, value = m.group(1), m.group(2)
# 2. чтение до «пустого» значения без дублирования вызова
while (chunk := f.read(8192)):
process(chunk)
# 3. comprehension без двойного вызова дорогой функции
results = [y for x in data if (y := expensive(x)) is not None]Без walrus последний пример пришлось бы писать как
[expensive(x) for x in data if expensive(x) is not None] — с двумя вызовами
на каждый элемент.
Подвох. Скобки почти всегда обязательны: приоритет := ниже, чем
у сравнения, поэтому if n := len(data) > 10 присвоит в n результат
сравнения (True), а не длину. И имя из walrus «живёт» в объемлющей области
(в comprehension — в той, где написан сам comprehension), в отличие от
переменной цикла comprehension (см. Comprehensions).
Глубже. Синтаксически := запрещён там, где легко спутать с =: на
верхнем уровне выражения-инструкции (x := 1 — SyntaxError, нужно
(x := 1)), в качестве именованного аргумента (f(x := 1) — это позиционный
аргумент, а f(x = 1) — именованный) и для присваивания атрибуту или элементу
(obj.attr := 1 нельзя). PEP 572 — та самая история, после которой Гвидо
ван Россум ушёл с поста BDFL.
Коротко. Всё до / — позиционно-только параметры (PEP 570, Python 3.8+):
их нельзя передать по имени. Вместе с * это даёт полный контроль над
способом передачи каждого аргумента.
# python 3.8+
def f(a, b, /, c, d, *, e):
...
# a, b — только позиционно
# c, d — как угодно
# e — только по имениf(1, 2, 3, d=4, e=5) # ok
f(a=1, b=2, c=3, d=4, e=5)
# TypeError: f() got some positional-only arguments
# passed as keyword arguments: 'a, b'Подвох. Это не «строгость ради строгости», а вопрос совместимости: имя позиционно-только параметра не является частью публичного API, и его можно переименовать в следующей версии, ничего не сломав. Обратное тоже верно — как только параметр можно передать по имени, имя становится обязательством.
Глубже. До 3.8 так умели только функции, написанные на C: сигнатуры вроде
len(obj, /) или dict.get(key, default, /) невыразимы обычным def, отсюда
и расхождение поведения между стандартной библиотекой и пользовательским
кодом. PEP 570 закрыл разрыв, а заодно освободил имена: def f(self, /, **kwargs)
позволяет принять kwargs с ключом "self", что раньше было невозможно.
Посмотреть сигнатуру целиком помогает inspect.signature:
import inspect
inspect.signature(f)
# <Signature (a, b, /, c, d, *, e)>