Skip to content

Latest commit

 

History

History
100 lines (76 loc) · 5.01 KB

File metadata and controls

100 lines (76 loc) · 5.01 KB

Walrus-оператор и позиционно-только параметры

← Функциональный стиль · 🏠 Домой · Декораторы →


Что такое walrus-оператор и зачем он нужен?

Коротко. := (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)>

← Функциональный стиль · 🏠 Домой · Декораторы →