Главная / Курсы / Python / Вариативные функции
# Глава 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. Здесь можно задавать вопросы и общаться.
Задонатить. Если вам нравится курс, вы можете поддержать развитие площадки!