ПрограммированиеFullstack Python разработчик

Объясните механизм работы операторов * и ** при распаковке последовательностей и словарей в Python. Каковы тонкие моменты и где это бывает полезно?

Проходите собеседования с ИИ помощником Hintsage

Ответ.

История вопроса:

Операторы * и ** для распаковки появились в Python давно, но их применение расширялось с каждой версией (например, с Python 3.5 добавлена поддержка объединения нескольких коллекций через * и **). Эти операторы делают работу с коллекциями гибче, в том числе при передаче аргументов и сборе их в функции.

Проблема:

Без распаковки при работе с динамическими последовательностями, передачей параметров с переменным количеством аргументов, возникает необходимость вручную писать циклы и проверять размерность коллекций. Ошибки легко появляются при некорректном использовании или если не различать назначение * и **.

Решение:

Оператор * предназначен для распаковки последовательностей (list, tuple, set), а оператор ** — для распаковки словарей при вызове функций или объединении нескольких словарей. Они позволяют элегантно передавать аргументы, делать слияние коллекций, легко превращать произвольные структуры в параметры функции.

Пример кода:

def foo(a, b, c): print(a, b, c) args = (1, 2, 3) foo(*args) # 1 2 3 params = {'a': 10, 'b': 20, 'c': 30} foo(**params) # 10 20 30 list1 = [1, 2] list2 = [3, 4] combined = [*list1, *list2] print(combined) # [1, 2, 3, 4]

Ключевые особенности:

  • *sequence передает элементы как отдельные позиционные аргументы, **dict как именованные аргументы.
  • Можно объединять и копировать коллекции элегантно через [*a, *b] и {**d1, **d2}.
  • Распаковка применяется как при определении/объявлении функций, так и при вызове.

Вопросы с подвохом.

Можно ли использовать * и ** для смешанных коллекций?

При вызове функции * работает только с позиционными, а ** только с именованными аргументами. Если передать нераспакованный dict как * или последовательность как **, возникнет ошибка.

def foo(a, b): print(a, b) foo(*{'a': 1, 'b': 2}) # Печатает: a b (ключи словаря, не значения!)

**Что будет, если имена ключей пересекаются при объединении dict через {**d1, d2}?

В результате будет значение из последнего словаря с таким ключом.

d1 = {'x': 1, 'y': 2} d2 = {'y': 33, 'z': 44} merged = {**d1, **d2} print(merged) # {'x': 1, 'y': 33, 'z': 44}

Можно ли использовать * и ** внутри списковых или словарных выражений?

Да, это законно начиная с Python 3.5, например:

lst = [1, 2, *range(3, 6)] # [1, 2, 3, 4, 5] dct = {**{'a': 1}, 'b': 2, **{'c': 3}}

Типовые ошибки и анти-паттерны

  • Передача dict через * (распакуются только ключи).
  • Избыточное пересечение именованных аргументов вызывает TypeError.
  • Попытка использовать ** для объекта, не являющегося mapping.

Пример из жизни

Негативный кейс

Девелопер принимает во входную функцию dict и разбирает его с помощью * вместо **. Происходит неожиданное поведение: в функцию попадают не значения, а ключи.

Плюсы:

Код не падает сразу, кажется “работающим”.

Минусы:

Скрытые ошибки и несоответствие ожидаемой логики.

Позитивный кейс

Корректная передача параметров через **kwargs, аккуратное слияние словарей, использование * при динамическом объединении последовательностей.

Плюсы:

Максимальная гибкость, лаконичность кода, простота рефакторинга.

Минусы:

При большом числе параметров и коллекций важно внимательно следить за именами и порядком, иначе возможны ошибки.