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

Введение Распаковка в Python относится к операции, которая состоит из присвоения итерации значений кортежу [/ lists-vs-tuples-in-python /] (или списку) переменных в одном операторе присваивания. В качестве дополнения можно использовать термин упаковка, когда мы собираем несколько значений в одной переменной с помощью итеративного оператора распаковки *. Исторически сложилось так, что разработчики Python обычно называли этот вид операции распаковкой кортежа. Однако, поскольку эта функция Python оказалась

Вступление

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

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

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

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

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

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

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

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

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

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

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

Когда мы помещаем кортежи по обе стороны от оператора присваивания, происходит операция распаковки кортежа. Значения справа присваиваются переменным слева в соответствии с их относительным положением в каждом tuple . Как вы можете видеть в приведенном выше примере, 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.

Когда мы распаковываем значения в переменные с помощью распаковки кортежа, количество переменных в левом tuple должно точно соответствовать количеству значений в правом tuple . В противном случае мы получим 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, что синтаксис был расширен для работы с любым итеративным объектом. Единственное требование состоит в том, чтобы итерация давала ровно один элемент для каждой переменной в принимающем tuple (или list ).

Ознакомьтесь со следующими примерами того, как итеративная распаковка работает в 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, мы можем использовать любую итерацию справа от оператора присваивания. Левая часть может быть заполнена tuple или list переменных. Посмотрите следующий пример, в котором мы используем tuple в правой части оператора присваивания:

 >>> [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' 

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

Упаковка с оператором *

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

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

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

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

Упаковка конечных значений в 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 
 <generator object <genexpr> 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 "<input>", line 1 
 SyntaxError: starred assignment target must be in a list or tuple 

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

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

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

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

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

Например, предположим, что у нас есть база данных о сотрудниках в нашей компании, и нам нужно назначить каждый элемент в списке описательной переменной. Если мы проигнорируем, как итеративная распаковка работает в 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, b = b, a мы переназначаем a на b и b на a в одной строке кода. Это намного удобнее и понятнее. Также обратите внимание, что при использовании этого метода нет необходимости в новой временной переменной.

Сбор нескольких значений с помощью *

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

 >>> 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 

Здесь волшебство first, *body, last = seq Оператор итеративной распаковки * собирает элементы в середине 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]) 

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

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

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

Если мы используем два или более * в выражении присваивания, то мы получим SyntaxError сообщающее нам, что найдено выражение, помеченное двумя звездами. Это так, потому что 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 . Но нам нужны только major , minor и micro чтобы наш скрипт работал, так что мы можем отказаться от остальных. Вот пример:

 >>> 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_set) + my_list + list(my_tuple) + list(range(1, 4)) + list(my_str) мы просто пишем [*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 присутствует в обоих словарях, преобладающее значение исходит от vowels , то есть самого правого словаря. Это происходит потому, что Python начинает добавлять пары ключ-значение слева направо. Если в процессе Python находит ключи, которые уже вышли, интерпретатор обновляет эти ключи новым значением. Вот почему в приведенном выше примере a

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

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

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

Продукт Цена Проданные единицы


Карандаш 0,25 1500 Ноутбук 1,30 550 Ластик 0,75 1000 ... ... ...

Из этой таблицы мы можем построить list двухэлементных кортежей. Каждый tuple будет содержать название продукта, цену и проданные единицы. Имея эту информацию, мы хотим рассчитать доход от каждого продукта. Для этого мы можем использовать такой цикл 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 

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

Давайте посмотрим на альтернативную реализацию с использованием распаковки в 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 Это делает наш код более читабельным и поддерживаемым, поскольку мы используем описательные имена для идентификации элементов каждого tuple . Это крошечное изменение позволит новичку-разработчику быстро понять логику кода.

Также можно использовать оператор * 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 . Затем оператор * list значений в своей целевой переменной 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, они не являются обязательными для работы этих методов. Синтаксис просто требует * или ** за которым следует действительный идентификатор. Итак, если вы можете дать этим аргументам осмысленные имена, то сделайте это. Это, безусловно, улучшит читаемость вашего кода.

Вызов функций с помощью * и **

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

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

 >>> 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 петель, вызовов функций и определений функций.

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus