Объяснение Python *args и **kwargs: гибкие функции с понятными примерами

  1. Главная
  2. Блог
  3. Разработка программного обеспечения

Разработка программного обеспечения

Объяснение Python *args и **kwargs: Гибкие функции с понятными примерами

Эти звезды в сигнатурах функций менее загадочны, чем кажутся. Узнайте, как *args и **kwargs собирают аргументы, как одни и те же звезды их распаковывают и как разрабатывать сигнатуры функций, которые трудно использовать неправильно.

8 мин чтения
Объяснение Python *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 требует определенного порядка:

  1. Обычные позиционные параметры
  2. *аргументы
  3. Параметры только для ключевых слов
  4. **кварги
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.

Продолжайте учиться

Работайте с Stack Quora

Нужна помощь с тестированием, WordPress или SEO?

Мы помогаем командам выпускать лучшее программное обеспечение и развивать свои сайты. Расскажите нам, над чем вы работаете, и мы предложим четкий следующий шаг.

Продолжайте читать

Больше руководств по тестирование, искусственный интеллект и современные разработки

Понравилась эта статья? Выберите тему и сразу приступайте к нашим новейшим руководствам.

Искусственный интеллект

Объяснение галлюцинаций искусственного интеллекта: почему языковые модели все выдумывают и как их уменьшить

Галлюцинации ИИ объяснили: что это такое, почему языковые модели их создают, распространенные типы, как уменьшить их с помощью заземления и подсказо…

Читать статью →
DevOps

Объяснение переменных среды: файлы .env, секреты и правильно выполненная конфигурация

Переменные среды объясняются на примерах Python, которые мы запускали: их чтение и установка, почему каждое значение является строкой, файлы .env, н…

Читать статью →
Разработка ПО

Объяснение ограничения скорости API: алгоритмы, заголовки и обработка ошибок 429

Ограничение скорости API объясняется с помощью проведенного нами моделирования: сегмент токенов, фиксированное окно, скользящее окно, 429 ответов, з…

Читать статью →
Автоматизация тестирования

Издевательство в Python: unittest.mock, патч и ошибки, из-за которых тесты лгут

Издевательство на Python с помощью unittest.mock, протестировано: Mock и return_value, Side_effect, исправление использования имени, спецификация и …

Читать статью →
Тестирование ПО

Объяснение покрытия кода: покрытие линий или ветвей и что на самом деле означает 100%

Покрытие кода объяснено с помощью реального запуска покрытия.py: покрытие строк, ветвей и условий, чтение отчета, почему 100%-ное покрытие все еще м…

Читать статью →
Базы данных и SQL

DELETE, TRUNCATE и DROP в SQL: различия, откат и когда использовать каждый из них

Объяснение DELETE, TRUNCATE и DROP на примере протестированного SQL: что каждый из них удаляет, откат, триггеры, сброс идентификаторов, скорость и ч…

Читать статью →
Базы данных и SQL

Объяснение представлений SQL: создавайте, используйте и избегайте ловушек, с примерами

Представления SQL, объясненные с помощью запросов, которые мы выполняли: создание представления, результаты в реальном времени, скрытие столбцов, пр…

Читать статью →
Разработка ПО

Интерфейс TypeScript и тип: реальные различия, протестированные с помощью компилятора

Интерфейс TypeScript против типа, проверенный реальным компилятором: что могут делать оба, что может делать только один, слияние объявлений, расшире…

Читать статью →
Разработка ПО

Объяснение Python *args и **kwargs: гибкие функции с понятными примерами

Python *args и **kwargs, объясненные с помощью кода, который мы запускали: упаковка и распаковка, порядок параметров, аргументы только по ключевым с…

Читать статью →
Разработка ПО

Лямбда-функции Python: синтаксис, реальное использование и когда их не следует использовать

Лямбда-функции Python, объясненные с помощью кода, который мы запускали: синтаксис, сортировка с помощью ключа, карты и фильтра, замыкания, ловушка …

Читать статью →
Искусственный интеллект

Что такое ИИ-агенты? Как они работают: можно запустить простой цикл агента

Что такое агенты ИИ? Простое руководство по циклу агента, инструментам, памяти и ограничениям, с небольшим циклом Python, который мы запустили, реал…

Читать статью →
DevOps

Мониторинг и наблюдаемость: реальная разница с журналами, метриками и трассировками

Объяснение мониторинга и наблюдаемости: известные и неизвестные сбои, журналы, метрики и трассировки, пошаговое руководство по инцидентам при оформл…

Читать статью →
DevOps

Docker Volumes против Bind Mounts: правильное сохранение данных контейнера

Объяснение томов Docker и монтирования привязки: почему исчезают данные контейнера, именованные тома, монтирование привязки, tmpfs, примеры создания…

Читать статью →
Автоматизация тестирования

TDD против BDD: ключевые различия, одна функция, созданная для обоих способов, и когда использовать каждый

TDD и BDD объясняются с помощью одной функции скидки, созданной в обоих направлениях: красно-зеленый рефакторинг с кодом, который мы запускали, сцен…

Читать статью →
Разработка ПО

Git merge vs rebase: отличия, примеры и когда что использовать

Git merge vs rebase с реальным выводом log, сравнительной таблицей, золотым правилом rebase и безопасным рабочим процессом для команды.

Читать статью →
Разработка ПО

Генераторы списков в Python: синтаксис, примеры и когда их избегать

Генераторы списков в Python: синтаксис, фильтры, if-else, вложенные циклы, измеренное время и случаи, когда обычный цикл for лучше.

Читать статью →
Базы данных и SQL

SQL JOIN: объяснение с примерами (Inner, Left, Right, Full)

Разберите SQL JOIN шаг за шагом на примерах таблиц и запросов: INNER, LEFT, RIGHT, FULL, CROSS и SELF, а также частые ошибки и советы по производите…

Читать статью →