Рубрики
Без рубрики

Распаковка в Python: За пределами параллельного назначения

Распаковка использует * для назначения нескольких переменных из одной итерации в одном операторе присваивания. Это делает ваш код Python чище и быстрее писать.

Автор оригинала: Leodanis Pozo Ramos.

Вступление

Распаковка в Python относится к операции, которая состоит в присвоении итеративного набора значений кортежу (или списку ) переменных в одном операторе присваивания. В качестве дополнения термин packing может быть использован, когда мы собираем несколько значений в одной переменной с помощью итеративного оператора распаковки, * .

Исторически разработчики Python обычно называли этот вид операции распаковкой кортежа . Однако, поскольку эта функция Python оказалась довольно полезной и популярной, она была обобщена на все виды итераций. В настоящее время более современным и точным термином была бы итеративная распаковка .

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

Кроме того, мы также рассмотрим некоторые практические примеры использования функции итеративной распаковки в контексте операций присваивания, для циклов, определений функций и вызовов функций.

Упаковка и распаковка в Python

Python позволяет кортежу (или списку ) переменных появляться в левой части операции присваивания. Каждая переменная в кортеже может получить одно значение (или несколько, если мы используем оператор * ) из итерации в правой части присваивания.

По историческим причинам разработчики Python обычно называли это распаковкой кортежа . Однако, поскольку эта функция была обобщена на все виды итераций, более точным термином будет iterable unpacking , и именно так мы будем называть ее в этом учебнике.

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

Распаковка кортежей

В Python мы можем поместить кортеж переменных в левую часть оператора присваивания ( = ) и кортеж значений в правую часть. Значения справа будут автоматически назначены переменным слева в соответствии с их положением в кортеже . Это обычно известно как распаковка кортежа в Python. Посмотрите на следующий пример:

>>> (a, b, c) = (1, 2, 3)
>>> a
1
>>> b
2
>>> c
3

Когда мы помещаем кортежи по обе стороны от оператора присваивания, происходит операция распаковки кортежа. Значения справа присваиваются переменным слева в соответствии с их относительным положением в каждом кортеже . Как вы можете видеть в приведенном выше примере, a будет 1 , b будет 2 , и c будет 3 .

Чтобы создать объект tuple , нам не нужно использовать пару круглых скобок () в качестве разделителей. Это также работает для распаковки кортежей, поэтому следующие синтаксисы эквивалентны:

>>> (a, b, c) = 1, 2, 3
>>> a, b, c = (1, 2, 3)
>>> a, b, c = 1, 2, 3

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

Когда мы распаковываем значения в переменные с помощью распаковки кортежа, количество переменных на левой стороне кортежа должно точно соответствовать количеству значений на правой стороне кортежа . В противном случае мы получим ValueError .

Например, в следующем коде мы используем две переменные слева и три значения справа. Это вызовет ValueError , сообщающий нам, что существует слишком много значений для распаковки:

>>> a, b = 1, 2, 3
Traceback (most recent call last):
  ...
ValueError: too many values to unpack (expected 2)

Примечание: Единственное исключение-это когда мы используем оператор * для упаковки нескольких значений в одну переменную, как мы увидим позже.

С другой стороны, если мы используем больше переменных, чем значений, то получим ValueError но на этот раз сообщение говорит о том, что недостаточно значений для распаковки:

>>> a, b, c = 1, 2
Traceback (most recent call last):
  ...
ValueError: not enough values to unpack (expected 3, got 2)

Если мы используем другое количество переменных и значений в операции распаковки кортежа, то получим ValueError . Это потому, что Python должен однозначно знать, какое значение входит в какую переменную, чтобы он мог выполнить соответствующее назначение.

Распаковка Итераций

Функция распаковки кортежей стала настолько популярной среди разработчиков Python, что синтаксис был расширен для работы с любым итеративным объектом. Единственное требование состоит в том, чтобы итерация давала ровно один элемент на переменную в принимающем кортеже (или списке ).

Ознакомьтесь со следующими примерами того, как итеративная распаковка работает в Python:

>>> # Unpacking strings
>>> a, b, c = '123'
>>> a
'1'
>>> b
'2'
>>> c
'3'
>>> # Unpacking lists
>>> a, b, c = [1, 2, 3]
>>> a
1
>>> b
2
>>> c
3
>>> # Unpacking generators
>>> gen = (i ** 2 for i in range(3))
>>> a, b, c = gen
>>> a
0
>>> b
1
>>> c
4
>>> # Unpacking dictionaries (keys, values, and items)
>>> my_dict = {'one': 1, 'two':2, 'three': 3}
>>> a, b, c = my_dict  # Unpack keys
>>> a
'one'
>>> b
'two'
>>> c
'three'
>>> a, b, c = my_dict.values()  # Unpack values
>>> a
1
>>> b
2
>>> c
3
>>> a, b, c = my_dict.items()  # Unpacking key-value pairs
>>> a
('one', 1)
>>> b
('two', 2)
>>> c
('three', 3)

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

>>> [a, b, c] = 1, 2, 3
>>> a
1
>>> b
2
>>> c
3

Он работает точно так же, если мы используем итератор range() :

>>> x, y, z = range(3)
>>> x
0
>>> y
1
>>> z
2

Несмотря на то, что это допустимый синтаксис Python, он обычно не используется в реальном коде и, возможно, немного сбивает с толку начинающих разработчиков Python.

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

>>> a, b, c = {'a', 'b', 'c'}
>>> a
'c'
>>> b
'b'
>>> c
'a'

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

Упаковка С Оператором *

Оператор * | известен в этом контексте как оператор распаковки кортежа (или итеративного)|/. Он расширяет функциональность распаковки, позволяя нам собирать или упаковывать несколько значений в одну переменную. В следующем примере мы упаковываем кортеж значений в одну переменную с помощью оператора * :

>>> *a, = 1, 2
>>> a
[1, 2]

Чтобы этот код работал, левая часть назначения должна быть кортежем (или списком ). Вот почему мы используем конечную запятую. Этот кортеж может содержать столько переменных, сколько нам нужно. Однако он может содержать только одно звездное выражение .

Мы можем сформировать испуганное выражение с помощью оператора распаковки * вместе с допустимым идентификатором Python, точно так же , как *a в приведенном выше коде. Остальные переменные в левой части кортежа называются обязательными переменными, потому что они должны быть заполнены конкретными значениями, иначе мы получим ошибку. Вот как это работает на практике.

Упаковка конечных значений в b :

>>> a, *b = 1, 2, 3
>>> a
1
>>> b
[2, 3]

Упаковка исходных значений в a :

>>> *a, b = 1, 2, 3
>>> a
[1, 2]
>>> b
3

Упаковка одного значения в a потому что b и c являются обязательными:

>>> *a, b, c = 1, 2, 3
>>> a
[1]
>>> b
2
>>> c
3

Упаковка никаких значений в a ( a по умолчанию [] ), поскольку b , c и d являются обязательными:

>>> *a, b, c, d = 1, 2, 3
>>> a
[]
>>> b
1
>>> c
2
>>> d
3

Не указывая значения для обязательной переменной ( e ), возникает ошибка:

>>> *a, b, c, d, e = 1, 2, 3
 ...
ValueError: not enough values to unpack (expected at least 4, got 3)

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

>>> gen = (2 ** x for x in range(10))
>>> gen
 at 0x7f44613ebcf0>
>>> *g, = gen
>>> g
[1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
>>> ran = range(10)
>>> *r, = ran
>>> r
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

В этих примерах оператор * упаковывает элементы в gen и ran в g и r соответственно. С его синтаксисом мы избегаем необходимости вызова list() для создания list значений из объекта range , выражения генератора или функции генератора.

Обратите внимание, что мы не можем использовать оператор распаковки * для упаковки нескольких значений в одну переменную без добавления конечной запятой к переменной в левой части присваивания. Таким образом, следующий код не будет работать:

>>> *r = range(10)
  File "", line 1
SyntaxError: starred assignment target must be in a list or tuple

Если мы попытаемся использовать оператор * для упаковки нескольких значений в одну переменную, то нам нужно использовать синтаксис singleton tuple . Например, чтобы приведенный выше пример работал, нам просто нужно добавить запятую после переменной r , как в *r,(10) .

Использование упаковки и распаковки на практике

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

Назначение параллельно

Одним из наиболее распространенных вариантов использования распаковки в Python является то, что мы можем назвать parallel assignment . Параллельное присвоение позволяет присваивать значения в итеративном коде кортежу (или списку ) переменных в одном элегантном операторе.

Например, предположим, что у нас есть база данных о сотрудниках нашей компании, и нам нужно присвоить каждому элементу списка описательную переменную. Если мы проигнорируем, как итеративная распаковка работает в Python, мы можем заставить себя писать такой код:

>>> employee = ["John Doe", "40", "Software Engineer"]
>>> name = employee[0]
>>> age = employee[1]
>>> job = employee[2]
>>> name
'John Doe'
>>> age
'40'
>>> job
'Software Engineer'

Несмотря на то, что этот код работает, обработка индекса может быть неуклюжей, трудной для ввода и запутанной. Более чистое, более читаемое и питоническое решение может быть закодировано следующим образом:

>>> name, age, job = ["John Doe", "40", "Software Engineer"]
>>> name
'John Doe'
>>> age
40
>>> job
'Software Engineer'

Используя распаковку в Python, мы можем решить проблему предыдущего примера с помощью одного простого и элегантного утверждения. Это крошечное изменение сделало бы наш код более легким для чтения и понимания для начинающих разработчиков.

Обмен Значениями Между Переменными

Еще одно элегантное применение распаковки в Python-это обмен значениями между переменными без использования временной или вспомогательной переменной. Например, предположим, что нам нужно поменять местами значения двух переменных a и b . Для этого мы можем придерживаться традиционного решения и использовать временную переменную для хранения значения, подлежащего замене, следующим образом:

>>> a = 100
>>> b = 200
>>> temp = a
>>> a = b
>>> b = temp
>>> a
200
>>> b
100

Эта процедура состоит из трех шагов и новой временной переменной. Если мы используем распаковку в Python, то мы можем достичь того же результата за один и краткий шаг:

>>> a = 100
>>> b = 200
>>> a, b = b, a
>>> a
200
>>> b
100

В операторе a,, a мы переназначаем a to b и b to a в одной строке кода. Это гораздо более читабельно и просто. Кроме того, обратите внимание, что при использовании этого метода нет необходимости в новой временной переменной.

Сбор Нескольких Значений С Помощью *

Когда мы работаем с некоторыми алгоритмами, могут возникнуть ситуации, в которых нам нужно разбить значения итерационной или последовательности на куски значений для дальнейшей обработки. В следующем примере показано, как использовать для этого операции list и slicing :

>>> seq = [1, 2, 3, 4]
>>> first, body, last = seq[0], seq[1:3], seq[-1]
>>> first, body, last
(1, [2, 3], 4)
>>> first
1
>>> body
[2, 3]
>>> last
4

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

>>> seq = [1, 2, 3, 4]
>>> first, *body, last = seq
>>> first, body, last
(1, [2, 3], 4)
>>> first
1
>>> body
[2, 3]
>>> last
4

Линия первая, *тело, делает здесь магию. Итеративный оператор распаковки, * , собирает элементы в середине seq в body . Это делает наш код более читаемым, удобным для обслуживания и гибким. Вы, возможно, думаете, почему более гибкий? Ну, предположим, что seq меняет свою длину в дороге и вам все равно нужно собрать средние элементы в body . В этом случае, поскольку мы используем распаковку в Python, для работы нашего кода не требуется никаких изменений. Посмотрите на этот пример:

>>> seq = [1, 2, 3, 4, 5, 6]
>>> first, *body, last = seq
>>> first, body, last
(1, [2, 3, 4, 5], 6)

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

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

>>> *head, a, b = range(5)
>>> head, a, b
([0, 1, 2], 3, 4)
>>> a, *body, b = range(5)
>>> a, body, b
(0, [1, 2, 3], 4)
>>> a, b, *tail = range(5)
>>> a, b, tail
(0, 1, [2, 3, 4])

Мы можем переместить оператор * в кортеж (или список ) переменных, чтобы собрать значения в соответствии с нашими потребностями. Единственное условие состоит в том, что Python может определить, какой переменной присвоить каждое значение.

Важно отметить, что мы не можем использовать более одного выражения stared в задании, если мы это сделаем, то получим SyntaxError следующим образом:

>>> *a, *b = range(5)
  File "", line 1
SyntaxError: two starred expressions in assignment

Если мы используем два или более * в выражении присваивания, то мы получим Синтаксическую ошибку , сообщающую нам, что было найдено выражение с двумя звездочками. Это происходит потому, что Python не может однозначно определить, какое значение (или значения) мы хотим присвоить каждой переменной.

Удаление Ненужных Значений С Помощью *

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

>>> a, b, *_ = 1, 2, 0, 0, 0, 0
>>> a
1
>>> b
2
>>> _
[0, 0, 0, 0]

Для более глубокого примера этого варианта использования предположим, что мы разрабатываем сценарий, который должен определить версию Python, которую мы используем. Для этого мы можем использовать атрибут sys.version_info . Этот атрибут возвращает кортеж, содержащий пять компонентов номера версии: major , minor , micro , releaselevel и serial . Но нам просто нужно мажор , минор и микро , чтобы наш сценарий работал, поэтому мы можем отбросить все остальное. Вот пример:

>>> import sys
>>> sys.version_info
sys.version_info(major=3, minor=8, micro=1, releaselevel='final', serial=0)
>>> mayor, minor, micro, *_ = sys.version_info
>>> mayor, minor, micro
(3, 8, 1)

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

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

Возврат кортежей в функциях

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

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

>>> def powers(number):
...     return number, number ** 2, number ** 3
...
>>> # Packing returned values in a tuple
>>> result = powers(2)
>>> result
(2, 4, 8)
>>> # Unpacking returned values to multiple variables
>>> number, square, cube = powers(2)
>>> number
2
>>> square
4
>>> cube
8
>>> *_, cube = powers(2)
>>> cube
8

Если мы определим функцию, которая возвращает значения, разделенные запятыми, то мы можем выполнить любую операцию упаковки или распаковки этих значений.

Слияние Итераций С Оператором *

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

>>> my_tuple = (1, 2, 3)
>>> (0, *my_tuple, 4)
(0, 1, 2, 3, 4)
>>> my_list = [1, 2, 3]
>>> [0, *my_list, 4]
[0, 1, 2, 3, 4]
>>> my_set = {1, 2, 3}
>>> {0, *my_set, 4}
{0, 1, 2, 3, 4}
>>> [*my_set, *my_list, *my_tuple, *range(1, 4)]
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
>>> my_str = "123"
>>> [*my_set, *my_list, *my_tuple, *range(1, 4), *my_str]
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, '1', '2', '3']

Мы можем использовать оператор итеративной распаковки * при определении последовательностей для распаковки элементов подпоследовательности (или итеративной) в конечную последовательность. Это позволит нам создавать последовательности на лету из других существующих последовательностей без вызова таких методов, как append () , insert () и т. Д.

Последние два примера показывают, что это также более читаемый и эффективный способ объединения итераций. Вместо того чтобы писать list(my_site) + my_lust + list(my_tuple) + list(range(1, 4)) + list(my_store) мы просто пишем [*my_set, *my_list, *my_tuple, *range(1, 4), *my_str] .

Распаковка Словарей С Помощью Оператора **

В контексте распаковки в Python оператор ** называется оператором распаковки словаря . Использование этого оператора было расширено PEP 448 . Теперь мы можем использовать его в вызовах функций, в понятиях и выражениях генератора, а также в дисплеях .

Основным вариантом использования оператора распаковки словаря является объединение нескольких словарей в один окончательный словарь с одним выражением. Давайте посмотрим, как это работает:

>>> numbers = {"one": 1, "two": 2, "three": 3}
>>> letters = {"a": "A", "b": "B", "c": "C"}
>>> combination = {**numbers, **letters}
>>> combination
{'one': 1, 'two': 2, 'three': 3, 'a': 'A', 'b': 'B', 'c': 'C'}

Если мы используем оператор распаковки словаря внутри отображения словаря, то мы можем распаковать словари и объединить их, чтобы создать окончательный словарь, который включает пары ключ-значение исходных словарей, точно так же, как мы сделали это в приведенном выше коде.

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

>>> letters = {"a": "A", "b": "B", "c": "C"}
>>> vowels = {"a": "a", "e": "e", "i": "i", "o": "o", "u": "u"}
>>> {**letters, **vowels}
{'a': 'a', 'b': 'B', 'c': 'C', 'e': 'e', 'i': 'i', 'o': 'o', 'u': 'u'}

Поскольку ключ a присутствует в обоих словарях, преобладающее значение происходит от гласных , который является самым правым словарем. Это происходит потому, что Python начинает добавлять пары ключ-значение слева направо. Если в процессе Python находит ключи, которые уже существуют, интерпретатор обновляет эти ключи новым значением. Вот почему значение ключа a в приведенном выше примере записано в нижнем регистре.

Распаковка в For-Loops

Мы также можем использовать итеративную распаковку в контексте циклов for . Когда мы запускаем цикл for , цикл назначает один элемент своей итерации целевой переменной в каждой итерации. Если назначаемый элемент является итеративным, то мы можем использовать кортеж целевых переменных. Цикл распакует итерацию под рукой в кортеж целевых переменных.

В качестве примера предположим, что у нас есть файл, содержащий данные о продажах компании следующим образом:

0.25 Карандаш 1500
1.30 Блокнот 550
0.75 Ластик 1000

Из этой таблицы мы можем построить список кортежей из двух элементов. Каждый кортеж будет содержать название продукта, цену и проданные единицы. С помощью этой информации мы хотим рассчитать доход каждого продукта. Для этого мы можем использовать цикл for следующим образом:

>>> sales = [("Pencil", 0.22, 1500), ("Notebook", 1.30, 550), ("Eraser", 0.75, 1000)]
>>> for item in sales:
...     print(f"Income for {item[0]} is: {item[1] * item[2]}")
...
Income for Pencil is: 330.0
Income for Notebook is: 715.0
Income for Eraser is: 750.0

Этот код работает так, как и ожидалось. Однако мы используем индексы, чтобы получить доступ к отдельным элементам каждого кортежа . Это может быть трудно прочитать и понять начинающим разработчикам.

Давайте рассмотрим альтернативную реализацию с использованием распаковки в Python:

>>> for product, price, sold_units in sales:
...     print(f"Income for {product} is: {price * sold_units}")
...
Income for Pencil is: 330.0
Income for Notebook is: 715.0
Income for Eraser is: 750.0

Теперь мы используем итеративную распаковку в нашем цикле for . Это делает наш код более читаемым и поддерживаемым, потому что мы используем описательные имена для идентификации элементов каждого кортежа . Это крошечное изменение позволит начинающему разработчику быстро понять логику, лежащую в основе кода.

Также можно использовать оператор * в цикле for для упаковки нескольких элементов в одну целевую переменную:

>>> for first, *rest in [(1, 2, 3), (4, 5, 6, 7)]:
...     print("First:", first)
...     print("Rest:", rest)
...
First: 1
Rest: [2, 3]
First: 4
Rest: [5, 6, 7]

В этом цикле for мы ловим первый элемент каждой последовательности в first . Затем оператор * ловит список значений в своей целевой переменной rest .

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

>>> data = [((1, 2), 2), ((2, 3), 3)]
>>> for (a, b), c in data:
...     print(a, b, c)
...
1 2 2
2 3 3
>>> for a, b, c in data:
...     print(a, b, c)
...
Traceback (most recent call last):
  ...
ValueError: not enough values to unpack (expected 3, got 2)

В первом цикле структура целевых переменных, (a, b), c , согласуется со структурой элементов в итерационном цикле., ((1, 2), 2) . В этом случае цикл работает так, как и ожидалось. В отличие от этого, второй цикл использует структуру целевых переменных, которые не согласуются со структурой элементов в итерационном цикле, поэтому цикл терпит неудачу и вызывает ValueError .

Упаковка и распаковка в функциях

Мы также можем использовать функции упаковки и распаковки Python при определении и вызове функций. Это довольно полезный и популярный пример использования упаковки и распаковки в Python.

В этом разделе мы рассмотрим основы использования упаковки и распаковки в функциях Python либо в определении функции, либо в вызове функции.

Примечание: Для получения более глубокого и подробного материала по этим темам ознакомьтесь с аргументами переменной длины в Python с помощью *args и **kwargs .

Определение Функций С Помощью * и **

Мы можем использовать операторы * и ** в сигнатуре функций Python. Это позволит нам вызвать функцию с переменным числом позиционных аргументов ( * ) или с переменным числом аргументов ключевых слов, или и то, и другое. Рассмотрим следующую функцию:

>>> def func(required, *args, **kwargs):
...     print(required)
...     print(args)
...     print(kwargs)
...
>>> func("Welcome to...", 1, 2, 3, site='StackAbuse.com')
Welcome to...
(1, 2, 3)
{'site': 'StackAbuse.com'}

Приведенная выше функция требует по крайней мере одного аргумента с именем required . Он также может принимать переменное количество позиционных и ключевых аргументов. В этом случае оператор * собирает или упаковывает дополнительные позиционные аргументы в кортеж с именем args , а оператор ** собирает или упаковывает дополнительные аргументы ключевых слов в словарь с именем kwargs . Оба, args и kwargs , являются необязательными и автоматически по умолчанию имеют значения () и {} соответственно.

Несмотря на то, что имена args и kwargs широко используются сообществом Python, они не являются обязательным требованием для работы этих методов. Синтаксис просто требует * или ** с последующим допустимым идентификатором. Итак, если вы можете дать значимые имена этим аргументам, то сделайте это. Это, безусловно, улучшит читабельность вашего кода.

Вызов Функций С Помощью * и **

При вызове функций мы также можем извлечь выгоду из использования операторов * и ** для распаковки коллекций аргументов в отдельные позиционные или ключевые аргументы соответственно. Это обратная ситуация использования * и ** в сигнатуре функции. В сигнатуре операторы означают collect или pack переменное число аргументов в одном идентификаторе. В вызове они означают распаковать итерацию на несколько аргументов.

Вот основной пример того, как это работает:

>>> def func(welcome, to, site):
...     print(welcome, to, site)
...
>>> func(*["Welcome", "to"], **{"site": 'StackAbuse.com'})
Welcome to StackAbuse.com

Здесь оператор * распаковывает последовательности типа ["Welcome", "to"] в позиционные аргументы. Аналогично оператор ** распаковывает словари в аргументы, имена которых совпадают с ключами распакованного словаря.

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

>>> def func(required, *args, **kwargs):
...     print(required)
...     print(args)
...     print(kwargs)
...
>>> func("Welcome to...", *(1, 2, 3), **{"site": 'StackAbuse.com'})
Welcome to...
(1, 2, 3)
{'site': 'StackAbuse.com'}

Использование операторов * и ** при определении и вызове функций Python даст им дополнительные возможности и сделает их более гибкими и мощными.

Вывод

Итеративная распаковка оказывается довольно полезной и популярной функцией в Python. Эта функция позволяет нам распаковать итерацию на несколько переменных. С другой стороны, упаковка состоит из захвата нескольких значений в одну переменную с помощью оператора распаковки * .

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

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