Аргументы: *args, **kwargs, / и *
Виды параметров
def f(pos_only, /, normal, *args, kw_only, **kwargs):
...
| Вид | Как передаётся | Пример |
|---|---|---|
только позиционный (до /) |
только по позиции | len(obj) |
| обычный | по позиции или по имени | f(1) или f(x=1) |
*args |
все лишние позиционные — в кортеж | print(*values) |
только именованный (после * или *args) |
только по имени | sorted(x, key=..., reverse=...) |
**kwargs |
все лишние именованные — в словарь | dict(**fields) |
Порядок в объявлении строгий: позиционные → / → обычные → *args (или просто *) → только именованные → **kwargs.
*args
def log(level, *messages):
print(f"[{level}]", type(messages), messages)
print(f"[{level}]", " ".join(str(m) for m in messages))
log("INFO", "Сервер", "запущен", "на порту", 8000)
log("WARN")
*args собирает лишние позиционные аргументы в кортеж. Имя args — соглашение; важна звёздочка.
**kwargs
def create_user(name, **extra):
print(type(extra), extra)
user = {"name": name}
user.update(extra)
return user
print(create_user("Анна", age=28, city="Москва"))
print(create_user("Борис"))
**kwargs собирает лишние именованные аргументы в словарь (с сохранением порядка).
Только именованные параметры
Параметры после * (или после *args) можно передать только по имени. Это защищает от ошибок с флагами:
def send(to, subject, *, urgent=False, retries=3):
return f"→ {to}: {subject} urgent={urgent} retries={retries}"
print(send("anna@x.ru", "Отчёт", urgent=True))
print(send("anna@x.ru", "Отчёт", True)) # TypeError: что значило бы True — непонятно
Правило хорошего API
Булевы флаги и редкие настройки делайте только именованными: вызов send(..., urgent=True) читается однозначно, а send(..., True, 5) — нет.
Только позиционные параметры (Python 3.8+)
Параметры до / нельзя передать по имени. Так можно позже переименовать параметр, не ломая чужой код, и принимать имя name в **kwargs:
def distance(x, y, /):
return (x ** 2 + y ** 2) ** 0.5
print(distance(3, 4))
print(distance(x=3, y=4)) # TypeError: positional-only arguments passed as keyword
Многие встроенные функции так устроены: len(obj=[1]) не работает.
Распаковка при вызове
def point(x, y, z=0):
return f"({x}, {y}, {z})"
coords = [1, 2, 3]
opts = {"y": 20, "z": 30}
print(point(*coords)) # позиционно из списка
print(point(10, **opts)) # по имени из словаря
print(point(*[1], **{"y": 2}))
print(*["a", "b", "c"], sep="-") # print принимает *values
Проброс аргументов
Комбинация *args, **kwargs позволяет написать функцию-обёртку, которая передаёт любые аргументы дальше, не зная их состава. Это основа декораторов:
def logged(func, *args, **kwargs):
print(f"Вызов {func.__name__} с args={args} kwargs={kwargs}")
result = func(*args, **kwargs)
print(f"Результат: {result}")
return result
logged(pow, 2, 10)
logged(sorted, [3, 1, 2], reverse=True)
Ошибки, которые стоит узнавать
def f(a, b):
return a + b
for call in ["f(1)", "f(1, 2, 3)", "f(1, a=2)", "f(1, c=2)"]:
try:
eval(call)
except TypeError as e:
print(f"{call:12} → {e}")
Итог
*args— кортеж лишних позиционных,**kwargs— словарь лишних именованных.- После
*— только именованные параметры; до/— только позиционные. - При вызове
*seqи**mappingраспаковывают аргументы. def wrapper(*args, **kwargs): return func(*args, **kwargs)— универсальный проброс.
Практика решено 0 из 5
Напишите функцию total(*nums), которая принимает любое количество чисел и возвращает их сумму.
total() → 0, total(1, 2, 3) → 6.
Какие тесты проверяют код (4)
assert total() == 0 assert total(5) == 5 assert total(1, 2, 3) == 6 assert total(*range(101)) == 5050
def total(*nums):
result = 0
for n in nums:
result += n
return result
Напишите функцию make_tag(tag, text, **attrs), которая возвращает HTML-тег.
Атрибуты выводятся в порядке передачи: make_tag("a", "Сайт", href="/", target="_blank") →
'<a href="/" target="_blank">Сайт</a>'. Без атрибутов: make_tag("b", "x") → '<b>x</b>'.
Какие тесты проверяют код (2)
assert make_tag("b", "x") == "<b>x</b>"
assert make_tag("a", "Сайт", href="/", target="_blank") == '<a href="/" target="_blank">Сайт</a>'
def make_tag(tag, text, **attrs):
attr_str = "".join(f' {k}="{v}"' for k, v in attrs.items())
return f"<{tag}{attr_str}>{text}</{tag}>"
def f(a, b=2, *args, c, d=4, **kwargs): ... — какой вызов допустим?
| Допустим | TypeError | |
|---|---|---|
f(1, c=3) |
||
f(1) |
||
f(1, 2, 3, 4, c=5, x=6) |
||
f(c=3, a=1) |
||
f(1, 2, 3) |
Напишите функцию connect(host, port=5432, *, timeout=10, ssl=False): timeout и ssl должны
передаваться только по имени. Функция возвращает строку "host:port timeout=… ssl=…".
Какие тесты проверяют код (3)
assert connect("db") == "db:5432 timeout=10 ssl=False"
assert connect("db", 6543, ssl=True) == "db:6543 timeout=10 ssl=True"
try:
connect("db", 1, 30)
ok = False
except TypeError:
ok = True
assert ok, "connect(\"db\", 1, 30) должен вызывать TypeError: timeout — только по имени"
def connect(host, port=5432, *, timeout=10, ssl=False):
return f"{host}:{port} timeout={timeout} ssl={ssl}"
Что означает / в def pow(base, exp, /, mod=None)?
Войдите, чтобы отмечать пройденные уроки.