PythonMiddleTechnical

Чем positional-only, positional-or-keyword и keyword-only arguments отличаются друг от друга?

До / — positional-only (нельзя по имени), после * — keyword-only (нельзя позиционно), между ними — обычные positional-or-keyword. / появился в Python 3.8 (PEP 570), * — давно. Используйте для контроля API: positional-only защищает имена параметров, keyword-only — флаги.

Три категории параметров

Сигнатура функции в Python состоит из трёх зон, разделённых двумя маркерами:

def f(pos1, pos2, /, pos_or_kw, *, kw1, kw2):
    ...
#       ↑                ↑           ↑
#  positional-only    обычные    keyword-only
  • До /: positional-only (PEP 570, Python 3.8+). Можно передавать ТОЛЬКО позиционно. Имя параметра — внутренняя деталь, его можно менять без слома API.
  • Между / и *: positional-or-keyword (обычные). Можно так и так.
  • После * (или *args): keyword-only. Можно передавать ТОЛЬКО по имени.

Пример

def connect(host, port, /, db="postgres", *, timeout=5, ssl=False):
    return {"host": host, "port": port, "db": db, "timeout": timeout, "ssl": ssl}


# OK: host/port позиционно; db — любым способом; timeout/ssl — только по имени
print(connect("localhost", 5432))
print(connect("localhost", 5432, "users", timeout=10))
print(connect("localhost", 5432, db="users", ssl=True))

# Ошибки:
try:
    connect(host="localhost", port=5432)
except TypeError as e:
    print(e)  # got some positional-only arguments passed as keyword arguments

try:
    connect("localhost", 5432, "users", 10)  # timeout позиционно — нельзя
except TypeError as e:
    print(e)  # takes from 2 to 3 positional arguments but 4 were given

Зачем positional-only

  • Защита имён параметров от API-зависимости. str.replace(self, old, new, count=-1, /) — можно переименовать old без слома существующих вызовов.
  • Совместимость с C API: builtin функции (например len(obj, /)) исторически positional-only.
  • Когда имя параметра в сигнатуре длинное/некрасивое, а пользователю позиции достаточно: round(number, ndigits=None, /).

Зачем keyword-only

  • Boolean флаги — самое важное применение: sorted(items, *, reverse=True) читается, sorted(items, True) — нет.
  • Когда у функции много опциональных параметров — заставляйте указывать имя для ясности и защиты от перестановки.
  • Forward-совместимость: вы добавите новый keyword-only параметр без риска поломать существующие позиционные вызовы.

Реальные примеры из stdlib

from collections import deque
# deque(iterable=(), maxlen=None) — maxlen keyword

# json.dumps(obj, *, skipkeys=False, ensure_ascii=True, ...)
# Все настройки сериализации — keyword-only

# print(*objects, sep=' ', end='\n', file=None, flush=False)
# *objects — positional varargs, остальное keyword-only

# dataclass(*, init=True, repr=True, eq=True, frozen=False, ...)
# Все параметры декоратора — keyword-only

varargs и kwargs

def f(*args, **kwargs):
    # все позиционные → args, все ключевые → kwargs
    pass


def g(a, *args, key, **kwargs):
    # a — позиционный; args собирает остаток позиционных
    # key — keyword-only (после *args автоматически)
    # kwargs — остаток keyword
    pass

Подводные камни

  • В обычном бизнес-коде злоупотреблять / — читаемость падает; используйте для библиотек и API.
  • Boolean флаги positional — типичный смрад: build(True, False). Делайте keyword-only.
  • Перестановка обычных параметров местами в minor-релизе — breaking; защищайтесь keyword-only.
  • def f(*, x=1) без позиционных перед * — корректно (только keyword), используйте смело.
  • inspect.signature(f).parameters показывает kind: POSITIONAL_ONLY, POSITIONAL_OR_KEYWORD, VAR_POSITIONAL, KEYWORD_ONLY, VAR_KEYWORD.
  • В 3.7 и ниже PEP 570 нет — синтаксис / даст SyntaxError; используйте *args+обёртку.

Common mistakes

  • Не знать смысл / и * в сигнатуре.
  • Путать keyword-only с **kwargs.
  • Не объяснять пользу для API-дизайна.

What the interviewer is testing

  • Может прочитать сложную сигнатуру.
  • Понимает ошибки связывания аргументов.
  • Знает практические причины keyword-only.

Sources

Related topics