Звёздный Python: где и как используются * и **
В Python много где можно встретить операторы * и **, которые в зависимости от контекста дают разный эффект. Разбираемся, как и где использовать «звёздочки».
227К открытий245К показов
В Python много где можно встретить *
и **
. Два этих оператора порой могут быть загадкой как для начинающих программистов, так и для тех, кто пришёл в Python из других языков, не имеющих точно таких же операторов. Сегодня мы поговорим о том, как их можно использовать.
Что мы не собираемся обсуждать
Речь пойдёт о префиксных, а не об инфиксных операторах. То есть мы не собираемся обсуждать умножение и возведение в степень:
О чём пойдёт речь
Мы обсудим префиксные операторы *
и **
, которые используются перед переменными. Например:
Здесь показаны два примера использования *
и ни одного примера для **
. В их число входит:
- Использование
*
и**
для передачи аргументов в функцию; - Использование
*
и**
для сбора переданных в функцию аргументов; - Использование
**
для принятия только именованных аргументов; - Использование
*
при распаковке кортежей; - Использование
*
для распаковки итерируемых объектов в список/кортеж; - Использование
**
для распаковки словарей в другие словари.
Даже если вы считаете, что вам знакомы все эти способы использования *
и **
, не будет лишним посмотреть все примеры ниже, чтобы убедиться в этом. С течением времени разработчики языка добавляли новые возможности для этих операторов, поэтому не будет ничего удивительного в том, что вы упустили новые варианты их использования.
Прим. перев. Примеры кода в статье предполагают использование Python 3.
Звёздочки для распаковки в аргументы функции
При вызове функции можно использовать оператор *
для распаковки итерируемого объекта в аргументы вызова:
Строка print(*fruits)
передаёт все элементы списка fruits
в вызов print()
как отдельные аргументы, поэтому нам даже не нужно знать, сколько элементов в списке.
Здесь оператор *
— не просто синтаксический сахар. Без фиксированной длины списка было бы невозможно передать элементы итерируемого объекта как отдельные аргументы, не используя *
.
Вот другой пример:
Здесь мы принимаем список со списками и возвращаем «транспонированный» список.
Оператор **
делает что-то похожее, только с именованными аргументами. Он позволяет взять словарь с парами ключ-значение и распаковать его в именованные аргументы в вызове функции:
Начиная с Python 3.5 *
и **
можно использовать несколько раз в вызове функции.
Порой бывает полезно использовать *
несколько раз:
Похожим образом используются **
:
Следует соблюдать осторожность при многократном использовании **
. В Python функции не могут иметь несколько одинаковых именованных аргументов, поэтому ключи в словарях не должны пересекаться, иначе будет выброшено исключение.
Звёздочки для упаковки аргументов, переданных в функцию
При определении функции можно использовать *
, чтобы собрать переменное количество позиционных аргументов, переданных в функцию. Они помещаются в кортеж:
Эта функция принимает любое количество аргументов:
Стандартные функции Python print()
и zip()
принимают любое количество позиционных аргументов. Благодаря *
мы можем написать свою функцию, работающую похожим образом.
В схожих целях можно применить и **
: если использовать этот оператор в объявлении функции, то он соберёт все переданные именованные аргументы в словарь:
Оператор **
соберёт все переданные именованные аргументы в словарь, на который ссылается аргумент attributes
:
Позиционные аргументы с только именованными аргументами
В Python 3 появился специальный синтаксис для только именованных (keyword-only) аргументов. Такие аргументы нельзя указать позиционно, только по имени.
Чтобы принимать только именованные аргументы, при определении функции мы можем расположить именованные аргументы после *
:
Эту функцию можно использовать следующим образом:
Аргументы dictionary
и default
идут после *keys
, а это значит, что их можно указать только как именованные аргументы. Если попытаться сделать иначе, мы получим ошибку:
Такое поведение было добавлено в PEP 3102.
Только именованные аргументы без позиционных
Описанный выше пример выглядит здорово, но что, если мы хотим получить только именованные аргументы без захвата неограниченного количества позиционных?
Прим. перев. Если к этому моменту вы задались вопросом, зачем вообще нужны только именованные аргументы, то всё просто: вызов функции с именованными аргументами выглядит гораздо лучше — сразу можно понять, какой аргумент за что отвечает. Более того, при использовании позиционных аргументов вы вынуждены соблюдать их порядок, в то время как именованные аргументы можно расположить как угодно. Также они позволяют не указывать значения аргументов, у которых есть значения по умолчанию.
Python позволяет сделать это с помощью одинокой звёздочки:
Эта функция принимает аргумент iterable
, который можно указать позиционно (как первый аргумент) или по его имени, и аргумент fillvalue
, который является только именованным аргументом. Это значит, что мы можем вызвать with_previous()
вот так:
Но не так:
Эта функция принимает два аргумента, и один из них, fillvalue
, должен быть именованным.
Встроенная функция sorted()
использует этот подход. Если посмотреть справку по этой функции, мы увидим следующее:
Прим. перев. Нечто аналогичное можно сделать и для позиционных аргументов. Как вы могли заметить, в определении sort()
используется /
. Он нужен для того, чтобы предшествующие аргументы можно было передавать только как позиционные, а не по имени.
Звёздочки для распаковки
В Python 3 также появилась возможность использовать оператор *
для распаковки итерируемых объектов:
Распаковка может быть даже вложенной:
Вряд ли вам представится возможность так сделать, но, возможно, это и к лучшему.
Tакая функциональность была добавлена в PEP 3132.
Звёздочки в литералах списков
В Python 3.5 появились новые способы использования звёздочек. Одной из ключевых новых фич являлась возможность сложить итерируемый объект в новый список.
Допустим, у вас есть функция, которая принимает любую последовательность и возвращает список, состоящий из этой последовательности и её обратной копии, сконкатенированных вместе:
Здесь нам требуется несколько раз преобразовывать последовательности в списки, чтобы получить конечный результат. В Python 3.5 можно поступить по-другому:
Этот вариант избавляет нас от необходимости лишний раз вызывать list
и делает наш код более эффективным и читаемым.
Ещё пример:
Эта функция возвращает новый список, в котором первый элемент переданного списка (или другой последовательности) перемещается в конец нового списка.
Такой вариант использования оператора *
является отличной возможностью для конкатенации итерируемых объектов разных типов. Оператор *
работает с любым итерируемым объектом, в то время как оператор +
работает только с определёнными последовательностями, которые должны быть одного типа.
Мы не ограничены созданием списков. Мы также можем создавать новые кортежи или множества:
Обратите внимание, что в последней строке мы создаём новое множество из списка и генератора. До того как появилась возможность использовать *
подобным образом, не было другого простого способа сделать это в одну строку кода. Да, это было возможно, но до такой конструкции было непросто додуматься и сложно запомнить:
Двойные звёздочки в литералах словарей
В PEP 448 были также добавлены новые возможности для **
, благодаря которым стало возможным перемещение пар ключ-значение из одного словаря (словарей) в новый:
Однако это можно использовать не только для объединения двух словарей.
Например, мы можем скопировать словарь, параллельно добавляя в него новое значение:
Или скопировать/объединить словари, параллельно перезаписывая определённые значения:
Звёздочки — сила
Операторы *
и **
в Python не просто синтаксический сахар. Часть из того, что можно сделать с их помощью, можно достичь другими путями, но они, как правило, более громоздкие и ресурсоёмкие. А некоторые из возможностей, предоставляемых звёздочками, вовсе нельзя реализовать иначе: например, принять переменное количество аргументов в функцию без *
.
227К открытий245К показов