Разработка программного обеспечения
Объяснение Python *args и **kwargs: Гибкие функции с понятными примерами
Эти звезды в сигнатурах функций менее загадочны, чем кажутся. Узнайте, как *args и **kwargs собирают аргументы, как одни и те же звезды их распаковывают и как разрабатывать сигнатуры функций, которые трудно использовать неправильно.
*argsсобирает дополнительные позиционные аргументы в кортеж.**kwargsсобирает дополнительные аргументы ключевого слова в словарь.- Имена являются всего лишь соглашением, поскольку
*и**имеют значение. - Те же звёздочки распаковывают список или словарь при вызове функции.
Рано или поздно вы прочитаете функцию типа def handler(*args, **kwargs) и задаетесь вопросом, что она принимает. Python *args и **kwargs позволяют функции принимать гибкое количество аргументов. Они поддерживают декораторы, оболочки и многие библиотечные API.
В этом руководстве рассказывается, как они работают в обоих направлениях, а также о новых способах, которыми Python позволяет управлять вызовом функции. Мы запускали каждый пример на Python 3.13, и показанный результат получен в результате этого запуска.
Что делает *args
Параметр, записанный с одной звездочкой, собирает все дополнительные позиционные аргументы в кортеж:
def total(*args):
print(type(args).__name__, args)
return sum(args)
print(total(1, 2, 3))
# tuple (1, 2, 3)
# 6
print(total())
# tuple ()
# 0
Без аргументов вы просто получаете пустой кортеж, поэтому функция по-прежнему работает. Имя args является соглашением. *numbers будет вести себя так же, и зачастую лучше использовать более описательное имя.
Что делает **kwargs
Две звездочки собирают в словарь любые дополнительные аргументы ключевого слова:
def describe(**kwargs):
print(type(kwargs).__name__, kwargs)
describe(name="Asha", city="Pune")
# dict {'name': 'Asha', 'city': 'Pune'}
Поскольку это обычный словарь, вы можете просмотреть его, найти ключи с помощью get() и передать дальше. Ключи сохраняют порядок, в котором они были переданы.
Порядок параметров
Когда вы смешиваете типы параметров, Python требует определенного порядка:
- Обычные позиционные параметры
*аргументы- Параметры только для ключевых слов
**кварги
def order(item, qty=1, *extras, priority=False, **notes):
print(item, qty, extras, priority, notes)
order("pen")
# pen 1 () False {}
order("pen", 2, "gift wrap", "card", priority=True, note="leave at door")
# pen 2 ('gift wrap', 'card') True {'note': 'leave at door'}
Обратите внимание priority. Поскольку он идет после *extras, его можно установить только по имени, поэтому случайный позиционный аргумент никогда не сможет установить его случайно. Размещение **kwargs ранее не допускается, и наш тест выдал SyntaxError: «аргументы не могут следовать за аргументом var-keyword».
Распаковка при звонке
Те же звезды работают в обратном порядке на месте призыва. Одна звездочка распределяет список или кортеж на позиционные аргументы, а две звезды распределяют словарь на аргументы ключевых слов:
nums = [4, 5, 6]
total(*nums) # same as total(4, 5, 6) -> 15
settings = {"name": "Ben", "city": "Delhi"}
describe(**settings) # same as describe(name="Ben", city="Delhi")
Распаковка работает с любой функцией, а не только с теми, которые используют *args и **kwargs. Это удобно, когда конфигурация поступает в виде словаря:
def connect(host, port, timeout=5):
return f"{host}:{port} (timeout {timeout})"
connect(**{"host": "db.local", "port": 5432})
# 'db.local:5432 (timeout 5)'
connect(**{"host": "db.local", "port": 5432, "ssl": True})
# TypeError: connect() got an unexpected keyword argument 'ssl'
Эта ошибка полезна. Функция без **kwargs отклоняет неизвестные параметры, поэтому опечатка в имени параметра громко выдается, а не игнорируется молча.
Аргументы только по ключевым словам
Голая * в подписи без имени означает «все, что после этого, должно передаваться по ключевому слову»:
def make_user(name, *, admin=False):
return name, admin
make_user("Asha", admin=True)
# ('Asha', True)
make_user("Asha", True)
# TypeError: make_user() takes 1 positional argument but 2 were given
Это отличный способ защитить логические флаги. Вызов типа make_user("Asha", True) заставляет читателей догадываться, что означает True. Требование admin=True делает каждый вызов очевидным.
Только позиционные аргументы
Начиная с Python 3.8, / отмечает параметры, которые можно передавать только по позиции:
def area(width, height, /):
return width * height
area(3, 4) # 12
area(width=3, height=4)
# TypeError: area() got some positional-only arguments
# passed as keyword arguments: 'width, height'
Это позволяет библиотеке позже переименовывать эти параметры, не нарушая чей-либо код, поскольку никто не может зависеть от имен. Некоторые встроенные модули работают таким образом. Например, len(obj=[1]) не работает.
Совет: Полный шаблон — def f(pos_only, /, normal, *, kw_only). Вам редко понадобятся все три, но знание того, что означают / и *, поможет вам читать документацию и сигнатуры библиотек.
Декораторы и обертки
Это основная реальная причина использовать оба вместе. Оболочке часто приходится принимать любые аргументы, которые принимает исходная функция, и передавать их без изменений:
import functools, time
def timed(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
print(f"{func.__name__} took {time.perf_counter() - start:.4f}s")
return result
return wrapper
@timed
def slow_add(a, b, delay=0.01):
time.sleep(delay)
return a + b
print(slow_add(2, 3, delay=0.02))
# slow_add took 0.0205s
# 5
Обертке вообще не обязательно знать параметры slow_add, поэтому она работает для любой функции. Ваши временные показатели будут немного отличаться. Наше руководство по декораторам Python объясняет functools.wraps и другие детали.
Объединение параметров
** также работает внутри словарного литерала, что дает удобный способ применения переопределений поверх значений по умолчанию:
defaults = {"theme": "light", "lang": "en"}
def build(**overrides):
return {**defaults, **overrides}
build(theme="dark")
# {'theme': 'dark', 'lang': 'en'}
Более поздние ключи выигрывают, поэтому переопределение заменяет значение по умолчанию. Подклассы также используют этот шаблон, как в super().__init__(*args, **kwargs), для передачи дополнительных аргументов родительскому классу. Дополнительную информацию о super() см. в Классы и объекты Python.
Шпаргалка
| Синтаксис | Где | Значение |
|---|---|---|
защита f(*args) |
Определение | Соберите дополнительные позиционные аргументы в кортеж. |
защита f(**kwargs) |
Определение | Соберите дополнительные аргументы ключевого слова в словарь |
защита f(а, *, б) |
Определение | b предназначено только для ключевых слов |
Защиту f(а, /, b) |
Определение | a только позиционный |
f(*items) |
Позвонить | Распределить последовательность на позиционные аргументы |
f(**options) |
Позвонить | Распространение диктата на аргументы ключевых слов |
{**а, **б} |
Буквальный | Объединить словари |
[*а, *б] |
Буквальный | Объединить итерации в список |
Когда их следует избегать
У гибкости есть цена. Функция, определенная как def create(**kwargs), ничего не сообщает читателям о том, какие параметры существуют, и редакторы не могут их автоматически заполнять. Опечатки могут незаметно пройти через несколько слоев, прежде чем что-то выйдет из строя.
Поэтому отдавайте предпочтение явным параметрам, если вы знаете, что нужно функции. Используйте *args и **kwargs, когда вам действительно нужна гибкость: обертки, декораторы, пересылка аргументов в другую функцию или настоящий ввод переменной длины, например total(*numbers). Если функции требуется много именованных опций, dataclass также может быть более понятным.
Распространенные ошибки
Использование изменяемого значения по умолчанию
Речь идет не о звездах, но она часто появляется возле них. Значение по умолчанию создается один раз при определении функции, а затем распределяется между вызовами:
def bad(items=[]):
items.append(1)
return items
print(bad(), bad())
# [1, 1] [1, 1]
Оба вызова вернули один и тот же список, который к тому времени содержал два элемента. Используйте items=None и создайте новый список внутри функции.
Другие ошибки
- Забыл распаковать. При вызове
total(nums)передается один список, а не три номера. Напишитеtotal(*nums). - Проглатывает опечатки. Функция, которая принимает
**kwargs, но игнорирует неизвестные ключи, скрывает параметры с ошибками. Подтвердите их или избегайте**kwargs. - Двукратная передача одного и того же аргумента.
f(1, **{"a": 2}), гдеa— первый параметр, вызывает ошибку «несколько значений». - Не пересылка в обертке. Вызов
func(args, kwargs)передает кортеж и словарь. Правильная форма:func(*args, **kwargs).
Введите подсказки для *args и **kwargs.
Подсказки типов в *args и **kwargs описывают каждое отдельное значение, а не всю коллекцию. Многих это удивляет:
def total(*prices: float) -> float:
return sum(prices) # prices is tuple[float, ...]
def tag(**attrs: str) -> str:
return " ".join(f'{k}="{v}"' for k, v in attrs.items()) # dict[str, str]
total(1.5, 2.5) # 4.0
tag(id="x", role="button") # id="x" role="button"
Здесь prices рассматривается как кортеж чисел с плавающей запятой, а attrs — как словарь строк. Когда аргументы ключевого слова имеют разные типы, в Python 3.12 добавлен точный вариант: TypedDict в сочетании с Unpack.
from typing import TypedDict, Unpack
class Options(TypedDict, total=False):
timeout: float
retries: int
def fetch(url: str, **opts: Unpack[Options]) -> None: ...
Средство проверки типов, такое как mypy или Pyright, может затем отклонить fetch(url, retries="3") или имя опции с ошибкой, чего никогда не сможет сделать простая **kwargs. Однако поведение во время выполнения не меняется. Когда мы запустили fetch("u", retries="3") под Python 3.13, он выполнился без нареканий, поскольку сам Python не применяет подсказки типов.
Если вы документируете длинный список принятых ключей для **kwargs, рассмотрите возможность замены их явными параметрами, содержащими только ключевые слова. Редакторы могут затем автоматически заполнять их, и ошибки сразу же выявляются.
Итоги
Помните о двух направлениях. По определению, звезды собираются. При звонке распаковывают. Добавьте аргументы, содержащие только ключевые слова, чтобы сделать вызовы более понятными, и зарезервируйте **kwargs для кода, который действительно пересылает аргументы. Для практики напишите небольшой декоратор, который регистрирует аргументы каждого вызова, и используйте его в нескольких различных функциях.
Часто задаваемые вопросы
В чем разница между *args и **kwargs?
*args собирает дополнительные позиционные аргументы в кортеж. **kwargs собирает дополнительные аргументы ключевого слова в словарь. Функция может использовать оба вместе.
Должен ли я называть их аргами и кваргами?
Нет. Звезды выполняют свою работу, поэтому значения * и ** являются одинаково допустимыми. args и kwargs — это просто широко признанное соглашение.
В каком порядке должны идти *args и **kwargs?
Сначала обычные параметры, затем *args, затем параметры, содержащие только ключевые слова, и в последнюю очередь **kwargs. Любой другой порядок вызывает SyntaxError.
Что означает одиночный * в определении функции?
Голый * означает, что каждый параметр после того, как он должен быть передан по ключевому слову. Это делает вызовы более понятными, особенно для логических флагов.
Что означает / в функции Python?
Параметры перед / являются только позиционными, поэтому вызывающая сторона не может передавать их по имени. Эта функция была добавлена в Python 3.8.
Продолжайте учиться
Нужна помощь с тестированием, WordPress или SEO?
Мы помогаем командам выпускать лучшее программное обеспечение и развивать свои сайты. Расскажите нам, над чем вы работаете, и мы предложим четкий следующий шаг.
Больше руководств по тестирование, искусственный интеллект и современные разработки
Понравилась эта статья? Выберите тему и сразу приступайте к нашим новейшим руководствам.
Объяснение галлюцинаций искусственного интеллекта: почему языковые модели все выдумывают и как их уменьшить
Галлюцинации ИИ объяснили: что это такое, почему языковые модели их создают, распространенные типы, как уменьшить их с помощью заземления и подсказо…
Читать статью →DevOpsОбъяснение переменных среды: файлы .env, секреты и правильно выполненная конфигурация
Переменные среды объясняются на примерах Python, которые мы запускали: их чтение и установка, почему каждое значение является строкой, файлы .env, н…
Читать статью →Разработка ПООбъяснение ограничения скорости API: алгоритмы, заголовки и обработка ошибок 429
Ограничение скорости API объясняется с помощью проведенного нами моделирования: сегмент токенов, фиксированное окно, скользящее окно, 429 ответов, з…
Читать статью →Автоматизация тестированияИздевательство в Python: unittest.mock, патч и ошибки, из-за которых тесты лгут
Издевательство на Python с помощью unittest.mock, протестировано: Mock и return_value, Side_effect, исправление использования имени, спецификация и …
Читать статью →Тестирование ПООбъяснение покрытия кода: покрытие линий или ветвей и что на самом деле означает 100%
Покрытие кода объяснено с помощью реального запуска покрытия.py: покрытие строк, ветвей и условий, чтение отчета, почему 100%-ное покрытие все еще м…
Читать статью →Базы данных и SQLDELETE, TRUNCATE и DROP в SQL: различия, откат и когда использовать каждый из них
Объяснение DELETE, TRUNCATE и DROP на примере протестированного SQL: что каждый из них удаляет, откат, триггеры, сброс идентификаторов, скорость и ч…
Читать статью →Базы данных и SQLОбъяснение представлений SQL: создавайте, используйте и избегайте ловушек, с примерами
Представления SQL, объясненные с помощью запросов, которые мы выполняли: создание представления, результаты в реальном времени, скрытие столбцов, пр…
Читать статью →Разработка ПОИнтерфейс TypeScript и тип: реальные различия, протестированные с помощью компилятора
Интерфейс TypeScript против типа, проверенный реальным компилятором: что могут делать оба, что может делать только один, слияние объявлений, расшире…
Читать статью →Разработка ПООбъяснение Python *args и **kwargs: гибкие функции с понятными примерами
Python *args и **kwargs, объясненные с помощью кода, который мы запускали: упаковка и распаковка, порядок параметров, аргументы только по ключевым с…
Читать статью →Разработка ПОЛямбда-функции Python: синтаксис, реальное использование и когда их не следует использовать
Лямбда-функции Python, объясненные с помощью кода, который мы запускали: синтаксис, сортировка с помощью ключа, карты и фильтра, замыкания, ловушка …
Читать статью →Искусственный интеллектЧто такое ИИ-агенты? Как они работают: можно запустить простой цикл агента
Что такое агенты ИИ? Простое руководство по циклу агента, инструментам, памяти и ограничениям, с небольшим циклом Python, который мы запустили, реал…
Читать статью →DevOpsМониторинг и наблюдаемость: реальная разница с журналами, метриками и трассировками
Объяснение мониторинга и наблюдаемости: известные и неизвестные сбои, журналы, метрики и трассировки, пошаговое руководство по инцидентам при оформл…
Читать статью →DevOpsDocker Volumes против Bind Mounts: правильное сохранение данных контейнера
Объяснение томов Docker и монтирования привязки: почему исчезают данные контейнера, именованные тома, монтирование привязки, tmpfs, примеры создания…
Читать статью →Автоматизация тестированияTDD против BDD: ключевые различия, одна функция, созданная для обоих способов, и когда использовать каждый
TDD и BDD объясняются с помощью одной функции скидки, созданной в обоих направлениях: красно-зеленый рефакторинг с кодом, который мы запускали, сцен…
Читать статью →Разработка ПОGit merge vs rebase: отличия, примеры и когда что использовать
Git merge vs rebase с реальным выводом log, сравнительной таблицей, золотым правилом rebase и безопасным рабочим процессом для команды.
Читать статью →Разработка ПОГенераторы списков в Python: синтаксис, примеры и когда их избегать
Генераторы списков в Python: синтаксис, фильтры, if-else, вложенные циклы, измеренное время и случаи, когда обычный цикл for лучше.
Читать статью →Базы данных и SQLSQL JOIN: объяснение с примерами (Inner, Left, Right, Full)
Разберите SQL JOIN шаг за шагом на примерах таблиц и запросов: INNER, LEFT, RIGHT, FULL, CROSS и SELF, а также частые ошибки и советы по производите…
Читать статью →