Звёздный Python: где и как используются * и **
В Python много где можно встретить операторы * и **, которые в зависимости от контекста дают разный эффект. Разбираемся, как и где использовать «звёздочки».
249К открытий285К показов
В 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 не просто синтаксический сахар. Часть из того, что можно сделать с их помощью, можно достичь другими путями, но они, как правило, более громоздкие и ресурсоёмкие. А некоторые из возможностей, предоставляемых звёздочками, вовсе нельзя реализовать иначе: например, принять переменное количество аргументов в функцию без *.
249К открытий285К показов



