Шаг 54.
Python: тонкости программирования.
Эффективные функции. Распаковка аргументов функции

    На этом шаге мы рассмотрим особенности выполнения этой операции.

    Действительно крутым, но немного загадочным функциональным средством языка является способность "распаковывать" аргументы функции из последовательностей и словарей при помощи операторов * и **.

    Давайте определим простую функцию для работы в качестве примера:

>>> def print_vector(x, y, z):
	print('<%s, %s, %s>' % (x, y, z))
Как вы видите, эта функция принимает три аргумента (x, y и z) и печатает их в приятно отформатированном виде. Мы можем применить эту функцию в нашей программе для структурной распечатки трехмерных векторов:
>>> print_vector(0, 1, 0)
<0, 1, 0>

    Нужно сказать, что в зависимости от того, какую структуру данных мы выбираем для представления трехмерных векторов, их распечатка нашей функцией print_vector() может восприниматься немного неуклюжей. Например, если наши векторы представлены в виде кортежей или списков, то во время их распечатки мы должны явным образом задавать индекс каждого компонента:

>>> tuple_vec = (1, 0, 1)
>>> list_vec = [1, 0, 1]
>>> print_vector(tuple_vec[0], tuple_vec[1], tuple_vec[2])
<0, 1, 0>

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

    (Разумеется, вы могли бы просто переопределить функцию print_vector() так, чтобы она принимала один-единственный параметр, представляющий векторный объект, но ради того, чтобы иметь простой пример, мы этот вариант пока проигнорируем.)

    К счастью, в Python имеется более подходящий способ справиться с этой ситуацией - при помощи распаковки аргументов функции с использованием оператора *:

>>> print_vector(*tuple_vec)
<0, 1, 0>
>>> print_vector(*list_vec)
<0, 1, 0>

    Размещение звездочки * перед итерируемым объектом в вызове функции его распакует и передаст его элементы как отдельные позиционные аргументы в вызванную функцию.

    Этот прием работает для любого итерируемого объекта, включая выражения-генераторы. В результате использования оператора * с генератором все поступающие из генератора элементы будут использованы и переданы в функцию:

>>> genexpr = (x * x for x in range(3))
>>> print_vector(*genexpr)
<0, 1, 4>

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

>>> dict_vec = {'y': 0, 'z': 1, 'x': 1}

    Этот объект-словарь можно передать в функцию print_vector() практически таким же образом, использовав оператор ** для распаковки:

>>> print_vector(**dict_vec)
<0, 1, 0>

    Поскольку словари не упорядочены, этот оператор соотносит значения словаря и аргументы функции на основе ключей словаря: аргумент x получает значение, связанное в словаре с 'x'.

    Если для распаковки словаря использовать оператор одинарной звездочки (*), то вместо этого ключи будут переданы в функцию в произвольном порядке:

>>> print_vector(*dict_vec)
<y, z, x>

    Функциональное средство языка Python, связанное с распаковкой аргументов функции, предоставляет вам большую гибкость. Зачастую это означает, что вам не придется реализовывать класс для необходимого вашей программе типа данных. При этом вполне достаточно обойтись простыми встроенными структурами данных, подобными кортежам или спискам, и, как результат, это поможет уменьшить сложность вашего программного кода.

    На следующем шаге мы подитожим изученный материал.




Предыдущий шаг Содержание Следующий шаг