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.