Аргументы функций в Python: позиционные, именованные, *args и **kwargs с примерами

Аргументы функций в Python: позиционные, именованные, *args и **kwargs с примерами

Аргументы функций в Python: позиционные, именованные, *args и **kwargs с примерами

Аргументы функций в Python — одна из базовых тем, которая напрямую влияет на читаемость кода и качество API. В этой статье мы подробно разберём позиционные и именованные аргументы, значения по умолчанию, *args и **kwargs, распаковку, а также более продвинутые вещи: позиционно‑только и ключево‑только параметры. Материал ориентирован на запрос: «аргументы функций в Python: *args и **kwargs, позиционные и именованные» и подойдет как новичкам, так и продолжающим разработчикам.

Кратко о терминах

  • Позиционные аргументы — передаются по порядку: f(1, 2).
  • Именованные (ключевые) аргументы — указываются по имени: f(x=1, y=2).
  • Значения по умолчанию — используются, если аргумент не передан: def f(x=10).
  • *args — собирает дополнительные позиционные аргументы в кортеж.
  • **kwargs — собирает дополнительные именованные аргументы в словарь.
  • Позиционно‑только (/) — аргументы, которые нельзя передать по имени.
  • Ключево‑только (* ) — аргументы, которые можно передавать только по имени.
  • Порядок параметров в сигнатуре

    Рекомендуемый порядок (PEP 8):

    def func(pos_only, /, standard, *args, kw_only, **kwargs):
        pass
    

    Здесь:

  • До символа / — позиционно‑только параметры (Python 3.8+).
  • Между / и * — обычные (могут быть и позиционно, и по имени).
  • После * — ключево‑только параметры (их можно передавать только по имени).
  • Позиционные и именованные аргументы: база

    def greet(name, greeting="Привет", *, upper=False):
        msg = f"{greeting}, {name}!"
        return msg.upper() if upper else msg
    
    print(greet("Анна"))                  # Привет, Анна!
    print(greet("Анна", "Здравствуйте"))   # Здравствуйте, Анна!
    print(greet("Анна", upper=True))       # ПРИВЕТ, АННА!
    

    Аргумент upper — ключево‑только. Его нельзя передать позиционно, что делает API явным и самодокументируемым.

    Значения по умолчанию и частая ошибка с изменяемыми типами

    Не используйте изменяемые объекты (list, dict, set) как значения по умолчанию. Они создаются один раз при определении функции:

    def add_item(item, bucket=[]):  # ПЛОХО
        bucket.append(item)
        return bucket
    
    print(add_item(1))  # [1]
    print(add_item(2))  # [1, 2] — неожиданное «накапливание»
    

    Правильно — использовать None и инициализировать внутри:

    def add_item(item, bucket=None):  # ХОРОШО
        if bucket is None:
            bucket = []
        bucket.append(item)
        return bucket
    

    *args и **kwargs: гибкость без хаоса

    *args собирает лишние позиционные аргументы, **kwargs — лишние именованные. Это полезно для обёрток и «проброса» параметров глубже по стеку.

    def log_call(fn):
        def wrapper(*args, **kwargs):
            print(f"CALL {fn.__name__} args={args} kwargs={kwargs}")
            return fn(*args, **kwargs)
        return wrapper
    
    @log_call
    def power(base, exp=2):
        return base ** exp
    
    print(power(3))             # CALL power args=(3,) kwargs={'exp': 2}
    print(power(2, exp=5))      # CALL power args=(2,) kwargs={'exp': 5}
    

    Совет: не злоупотребляйте *args/**kwargs. Явные параметры делают API безопаснее. Используйте их там, где нужна расширяемость, например в промежуточных слоях и декораторах.

    Распаковка при вызове: * и **

    Операторы * и ** применяются не только в сигнатуре, но и при вызове функции для распаковки последовательностей и словарей:

    def rectangle(area, /, *, color="blue", border=1):
        return {"area": area, "color": color, "border": border}
    
    data = (10 * 5,)         # кортеж из одного элемента
    options = {"color": "red", "border": 3}
    
    print(rectangle(*data, **options))  # {'area': 50, 'color': 'red', 'border': 3}
    

    Позиционно‑только и ключево‑только параметры

    Позиционно‑только параметры через «/» фиксируют интерфейс и упрощают рефакторинг: вы можете менять имена без риска сломать внешний код, зависящий от именованных аргументов.

    def normalize(value, /, *, method="minmax"):
        # value — только позиционно, method — только по имени
        ...
    

    Ключево‑только параметры через «*» повышают явность и читаемость, особенно для булевых флагов и параметров настройки.

    Практические мини‑кейсы

    1) Универсальная функция форматирования

    def format_price(value, /, *, currency="₽", sep=" ", precision=2):
        s = f"{value:,.{precision}f}".replace(",", "_").replace(".", ",").replace("_", sep)
        return f"{s}{sep}{currency}".strip()
    
    print(format_price(1234567.8))
    print(format_price(1234567.8, currency="USD", precision=0))
    

    2) Безопасная сборка SQL‑условий

    def build_where(**filters):
        # Пример: build_where(status='active', user_id=42)
        parts, params = [], []
        for field, value in filters.items():
            parts.append(f"{field} = %s")
            params.append(value)
        clause = " AND ".join(parts) if parts else "1=1"
        return clause, tuple(params)
    
    where, params = build_where(status="active", user_id=42)
    print(where)   # status = %s AND user_id = %s
    print(params)  # ('active', 42)
    

    3) CLI‑хелпер с пробросом опций

    def run_job(name, /, *, dry_run=False, verbose=False):
        if verbose:
            print(f"Running {name}; dry_run={dry_run}")
        if not dry_run:
            print("Job executed")
    
    cli_flags = {"dry_run": True, "verbose": True}
    run_job("cleanup", **cli_flags)
    

    Аннотации типов для *args и **kwargs

    С типами всё просто на базовом уровне:

    from typing import Any
    
    def mean(*values: float) -> float:
        if not values:
            raise ValueError("empty")
        return sum(values) / len(values)
    
    # **kwargs всегда со строковыми ключами
    from typing import Mapping
    
    def configure(**options: Any) -> None:
        # options: dict[str, Any]
        print(options)
    

    Для более строгой типизации ключевых параметров используют TypedDict и Unpack (Python 3.11+), но для «основ» достаточно подхода выше.

    Частые ошибки и ловушки

  • Изменяемые значения по умолчанию (list/dict/set) — используйте None и инициализацию внутри функции.
  • Смена порядка аргументов без сохранения обратной совместимости — добавляйте новые настройки как ключево‑только параметры.
  • Злоупотребление *args/**kwargs — усложняет автодополнение и проверку типов; предпочитайте явные параметры, если интерфейс стабилен.
  • Конфликт имен при распаковке **dict — следите, чтобы ключи не дублировали уже переданные именованные аргументы.
  • Мини‑чек‑лист при проектировании функции

  • Можно ли сделать часть параметров ключево‑только для читаемости?
  • Есть ли разумные значения по умолчанию?
  • Нужны ли позиционно‑только параметры для стабильности API?
  • Действительно ли требуется *args/**kwargs или лучше явные аргументы?
  • Добавьте аннотации типов и краткую docstring.
  • Заключение

    Грамотная работа с аргументами функций в Python делает код понятнее, API — стабильнее, а разработку — быстрее. Освойте позиционные и именованные аргументы, значения по умолчанию, *args и **kwargs, распаковку и паттерны «/» и «*». Эти знания постоянно применяются в реальных проектах — от веб‑бэкенда до анализа данных.

    Хотите быстрее и глубже прокачать основы с практикой и разбором задач? Посмотреть программу и записаться на курс «Python с Нуля до Гуру» — отличный следующий шаг.

    Источник

    НЕТ КОММЕНТАРИЕВ

    Оставить комментарий