# Глава 26. Вариативные функции
Вариативные функции принимают переменное число аргументов. Аргументы могут быть как позиционными, так и именованными. Для работы с аргументами вариативных функций применяется синтаксис распаковки и упаковки.
## Позиционные и именованные аргументы {#block-variadic}
Для передачи в функцию вариативных позиционных аргументов используется оператор `*`, вариативных именованных — `**`. Ма [рассматривали их](/courses/python/chapters/python_chapter_0250/) в главе про распаковку и упаковку.
Создадим функцию, принимающую один обязательный аргумент `x`, произвольное количество позиционных аргументов `args` и именованных аргументов `kwargs`. Вызовем ее:
```python {.example_for_playground}
def f(x, *args, **kwargs):
print(x)
print(type(args))
print(args)
print(type(kwargs))
print(kwargs)
f("x", 1, 2, 3, k1="a", k2="b")
```
```
x
<class 'tuple'>
(1, 2, 3)
<class 'dict'>
{'k1': 'a', 'k2': 'b'}
```
При вызове функции позиционные аргументы упаковались в кортеж, а именованные в словарь.
В этом примере мы вызвали вариативную функцию, передав в нее перечисление позиционных и именованных аргументов через запятую. Альтернативный вариант вызова — с помощью распаковки итерабельных объектов:
```python
def f(x, *args, **kwargs):
...
lst = [1, 2, 3]
d = {"k1": "a", "k2": "b"}
f("x", *lst, **d)
```
Операторы `*` и `**` в определении указывают, что при вызове функции перечисленные через запятую аргументы будут **упакованы** в коллекцию. Эти же операторы по месту вызова функции наоборот **распаковывают** передаваемую коллекцию для сопоставления отдельным аргументам.
Имена `args` и `kwargs` — это распространенные, но не обязательные обозначения позиционных и именованных (keyworded) аргументов. Вместо них в зависимости от контекста могут идти и более конкретные:
```python
def format(format_string, *params):
...
def analyze(**stats):
...
```
Напишите функцию `analyze()`, которая принимает позиционные и именованные аргументы. Функция должна: {.task_text}
- Определить, какие позиционные аргументы совпадают со значениями именованных аргументов.
- Вернуть список ключей этих именованных аргументов. Например, результатом вызова `analyze(1, 2, 3, k1=0, k2=3, k3=2, k4=1)` будет `['k2', 'k3', 'k4']`. {.task_text}
```python {.task_source #python_chapter_0260_task_0010}
```
Сигнатура функции: `analyze(*args, **kwargs)`. {.task_hint}
```python {.task_answer}
def analyze(*args, **kwargs):
res = []
for k, v in kwargs.items():
if v in args:
res.append(k)
return res
```
Что выведет этот код? {.task_text}
В случае исключения напишите `error`. {.task_text}
```python {.example_for_playground}
def append(x, *args):
args.append(x)
print(*args)
append(2, 0, 1)
```
```consoleoutput {.task_source #python_chapter_0260_task_0020}
```
`*args` — это кортеж, то есть неизменяемая коллекция. У кортежа нет метода `append()`, ведь он является модифицирующим. Поэтому при попытке добавить в кортеж значение будет сгенерировано исключение `AttributeError`. {.task_hint}
```python {.task_answer}
error
```
## Только именованные аргументы функций {#block-keyword-only}
Начиная с версии 3.0 в питоне [появилась](https://peps.python.org/pep-3102/) возможность указывать, какие аргументы передаются исключительно по имени и никак иначе. Для этого в синтаксисе языка было разрешено:
- Передавать вариативные аргументы **перед** обычными.
- Не указывать имя для вариативных аргументов, то есть вместо `*args` писать просто `*`.
Эти нововведения позволили использовать оператор `*` как визуальный разграничитель, слева от которого перечисляются только позиционные аргументы, а справа только именованные (keyword-only arguments):
```python {.example_for_playground}
def f(a1, a2, *, k1, k2):
...
f(1, 2, k1="val1", k2="val2")
```
Теперь при попытке передачи позиционных аргументов там, где должны быть именованные, интерпретатор сгенерирует ошибку:
```python {.example_for_playground}
def f(a1, a2, *, k1, k2):
...
f(1, 2, "val1", "val2")
```
```
Traceback (most recent call last):
File "example.py", line 4, in <module>
f(1, 2, "val1", "val2")
TypeError: f() takes 2 positional arguments but 4 were given
```
Что выведет этот код? {.task_text}
В случае исключения напишите `error`. {.task_text}
```python {.example_for_playground}
def sort_words(*words, case_sensitive=False):
key = str.lower if case_sensitive else None
return sorted(words, key=key)
print(sort_words("list", "Set", True))
```
```consoleoutput {.task_source #python_chapter_0260_task_0030}
```
`True` не присвоится параметру `case_sensitive`. Вместо этого он попадет последним элементом в кортеж `words`. Функция `sorted()` при попытке сравнения флага и строки упадет с исключением `TypeError: '<' not supported between instances of 'bool' and 'str'`. {.task_hint}
```python {.task_answer}
error
```
## Только позиционные аргументы функций
Итак, для формирования только именованных аргументов используется символ `*`. Именованные аргументы перечисляются справа от него.
А в версии 3.8 языка [были введены](https://peps.python.org/pep-0570/) только позиционные аргументы (positional-only arguments): они перечисляются слева от символа `/`:
```python
def f(a, b, c=None, /):
...
```
Почему в качестве разделителя был выбран символ `/`? Его предложил Гвидо ван Россум: раз уж для только именованных аргументов используется `*`, также обозначающий умножение, то почему бы не использовать для только позиционных аргументов обратный ему символ, обозначающий деление?
Обратили внимание, что только позиционные аргументы идут слева от `/`, а только именованные — справа от `*`? Благодаря этому в определении функции можно задавать и те, и другие:
```python
def f(positional_only, /, positional_or_keyword, *, keyword_only):
...
```
Найдите определение функции, в котором допущена ошибка, и закомментируйте его. {.task_text}
```python {.task_source #python_chapter_0260_task_0040}
def a(p1, p2, /):
pass
def b(p_or_kw, *, kw):
pass
def c(required, *, kw, /, p1, p2):
pass
def d(*, kw):
pass
```
Функция с ошибкой в определении: `c()`. {.task_hint}
```python {.task_answer}
def a(p1, p2, /):
pass
def b(p_or_kw, *, kw):
pass
# def c(required, *, kw, /, p1, p2):
# pass
def d(*, kw):
pass
```
Если для одного из только позиционных аргументов задано значение по умолчанию, то оно должно присутствовать и у всех следующих за ним позиционных аргументов.
Найдите определения функций, в которых допущены ошибки, и закомментируйте их. {.task_text}
```python {.task_source #python_chapter_0260_task_0050}
def a(p1, p2=None, /, p_or_kw, *, kw):
pass
def b(p1, p2=None, /, p_or_kw=None, *, kw):
pass
def c(p1, p2=None, /, *, kw):
pass
def d(p1=None, p2, /, p_or_kw=None, *, kw):
pass
def e(p1, p2=None, /):
pass
def f(p1=None, p2, /):
pass
```
Функции с ошибками в определении: `a()`, `d()`, `f()`. {.task_hint}
```python {.task_answer}
#def a(p1, p2=None, /, p_or_kw, *, kw):
# pass
def b(p1, p2=None, /, p_or_kw=None, *, kw):
pass
def c(p1, p2=None, /, *, kw):
pass
#def d(p1=None, p2, /, p_or_kw=None, *, kw):
# pass
def e(p1, p2=None, /):
pass
#def f(p1=None, p2, /):
# pass
```
Наибольшую пользу только позиционные аргументы приносят авторам библиотек:
- Если аргументы функции не несут особой смысловой нагрузки, они никогда не будут участвовать в вызове (то есть не появятся в пользовательском коде). Например, `min(arg1, arg2, /)`.
- При переименовании аргументов функции вызывающий код гарантированно не сломается: библиотека сохраняет обратную совместимость.
## Резюмируем
- Операторы `*` и `**` в определении функции запаковывают передаваемые аргументы в кортеж и словарь.
- Эти же операторы при вызове функции распаковывают итерабельные объекты для подстановки в качестве аргументов.
- Только именованные аргументы функций — это синтаксис, позволяющий через `*` отделять идущие слева произвольные аргументы от идущих справа именованных аргументов: `def f(p, *, k)`. Тогда при вызове именованный аргумент не сможет быть передан как позиционный.
- Только позиционные аргументы функций — это синтаксис, позволяющий через `/` отделять идущие слева от него только позиционные аргументы от идущих справа произвольных аргументов: `def f(p, /, k)`.
- В определении функции можно одновременно использовать только позиционные и только именованные аргументы: `def f(a, /, b, *, c)`.
Наша группа в telegram. Здесь можно задавать вопросы и общаться.
Задонатить. Если вам нравится курс, вы можете поддержать развитие площадки!