Percursos
/
Python
Python
/
Programa
/
Desempacotamento e atribuição múltipla
De

Desempacotamento e atribuição múltipla em Python

3 exercícios

Sobre Desempacotamento e atribuição múltipla

O desempacotamento refere-se ao ato de extrair os elementos de uma coleção, como uma list, uma tuple ou um dict, por meio de iteração. Os valores desempacotados podem depois ser atribuídos a variáveis na mesma instrução. Um exemplo muito comum deste comportamento é for item in list, em que item assume, ao longo da iteração, o valor de cada elemento da list, um a um.

A atribuição múltipla é a capacidade de atribuir várias variáveis a valores desempacotados numa única instrução. Isto torna o código mais conciso e legível, e faz-se separando com uma vírgula as variáveis a atribuir, como em first, second, third = (1,2,3) ou for index, item in enumerate(iterable).

Os operadores especiais * e ** são frequentemente usados em contextos de desempacotamento. O * pode ser usado para combinar várias lists/tuples numa única list/tuple, desempacotando cada uma numa nova list/tuple comum. O ** pode ser usado para combinar vários dicionários num só dicionário, desempacotando cada um num novo dict comum.

Quando o operador * é usado sem uma coleção, empacota uma série de valores numa list. Isto é frequentemente usado na atribuição múltipla para agrupar numa única variável todos os elementos "restantes" que não têm uma atribuição individual.

É comum em Python aproveitar também este comportamento de desempacotamento/empacotamento ao usar ou definir funções que recebem um número arbitrário de argumentos posicionais ou de palavra-chave. Verás muitas vezes estes parâmetros "especiais" definidos como def some_function(*args, **kwargs) e os argumentos "especiais" usados como some_function(*some_tuple, **some_dict).

Caution

*<variable_name> e **<variable_name> não devem ser confundidos com * e **. Enquanto * e ** são usados para a multiplicação e a exponenciação, respetivamente, *<variable_name> e **<variable_name> são usados como operadores de empacotamento e desempacotamento.

Atribuição múltipla

Na atribuição múltipla, o número de variáveis do lado esquerdo do operador de atribuição (=) tem de corresponder ao número de valores do lado direito. Para separar os valores, usa uma vírgula ,:

>>> a, b = 1, 2
>>> a
1

Se a atribuição múltipla receber um número incorreto de variáveis para os valores dados, é lançado um ValueError:

>>> x, y, z = 1, 2

ValueError: too many values to unpack (expected 3, got 2)

A atribuição múltipla não está limitada a um único tipo de dados:

>>> x, y, z = 1, "Hello", True
>>> x
1

>>> y
'Hello'

>>> z
True

A atribuição múltipla pode ser usada para trocar elementos em lists. Esta prática é bastante comum em algoritmos de ordenação. Por exemplo:

>>> numbers = [1, 2]
>>> numbers[0], numbers[1] = numbers[1], numbers[0]
>>> numbers
[2, 1]

Como as tuples são imutáveis, não podes trocar elementos numa tuple.

Desempacotamento

Note

Os exemplos abaixo usam lists, mas os mesmos conceitos aplicam-se a tuples.

Em Python, é possível desempacotar os elementos de uma list/tuple/dict em variáveis distintas. Como os valores aparecem nas lists/tuples numa ordem específica, são desempacotados para variáveis na mesma ordem:

>>> fruits = ["apple", "banana", "cherry"]
>>> x, y, z = fruits
>>> x
"apple"

Se houver valores que não são necessários, podes usar _ para os assinalar:

>>> fruits = ["apple", "banana", "cherry"]
>>> _, _, z = fruits
>>> z
"cherry"

Desempacotamento profundo

Desempacotar e atribuir valores de uma list/tuple dentro de uma list ou tuple (também conhecidas como listas/tuplas aninhadas) funciona da mesma forma que um desempacotamento superficial, mas muitas vezes precisa de qualificadores para clarificar o contexto ou a posição do valor:

>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>>> [[a, b], [c, d]] = fruits_vegetables
>>> a
"apple"

>>> d
"potato"

Também podes desempacotar profundamente apenas uma parte de uma list/tuple aninhada:

>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>>> [a, [c, d]] = fruits_vegetables
>>> a
["apple", "banana"]

>>> c
"carrot"

Se o desempacotamento tiver variáveis com uma colocação incorreta e/ou um número incorreto de valores, vais obter um ValueError:

>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>>> [[a, b], [d]] = fruits_vegetables

ValueError: too many values to unpack (expected 1)

Desempacotar uma list/tuple com *

Ao desempacotar uma list/tuple, podes usar o operador * para capturar os valores "restantes". Isto é mais claro do que fatiar a list/tuple (o que, em algumas situações, é menos legível). Por exemplo, podemos extrair o primeiro elemento e empacotar os valores restantes numa nova list sem o primeiro elemento:

>>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>>> x, *last = fruits
>>> x
"apple"

>>> last
["banana", "cherry", "orange", "kiwi", "melon", "mango"]

Também podemos extrair os valores do início e do fim da list e, ao mesmo tempo, agrupar todos os valores do meio:

>>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>>> x, *middle, y, z = fruits
>>> y
"melon"

>>> middle
["banana", "cherry", "orange", "kiwi"]

Também podemos usar * no desempacotamento profundo:

>>> fruits_vegetables = [["apple", "banana", "melon"], ["carrot", "potato", "tomato"]]
>>> [[a, *rest], b] = fruits_vegetables
>>> a
"apple"

>>> rest
["banana", "melon"]

Desempacotar um dicionário

Desempacotar um dicionário é um pouco diferente de desempacotar uma list/tuple. A iteração sobre dicionários assume, por omissão, as chaves. Por isso, ao desempacotar um dict, só podes desempacotar as chaves e não os valores:

>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> x, y, z = fruits_inventory
>>> x
"apple"

Se quiseres desempacotar os valores, podes usar o método <dict>.values():

>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> x, y, z = fruits_inventory.values()
>>> x
6

Se precisares tanto das chaves como dos valores, usa o método <dict>.items(). <dict>.items() gera uma vista iterável que contém pares chave-valor. Estes podem ser desempacotados numa tuple:

>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> x, y, z = fruits_inventory.items()
>>> x
("apple", 6)

Empacotamento

O empacotamento é a capacidade de agrupar vários valores numa única list que é atribuída a uma variável. Isto é útil quando queres desempacotar valores, fazer alterações e depois empacotar os resultados de volta numa variável. Também torna possível fazer fusões de duas ou mais lists/tuples/dicts.

Empacotar uma list/tuple com *

Empacotar uma list/tuple pode ser feito com o operador *. Isto empacota todos os valores numa list/tuple.

>>> fruits = ("apple", "banana", "cherry")
>>> more_fruits = ["orange", "kiwi", "melon", "mango"]

# fruits and more_fruits are unpacked and then their elements are packed into combined_fruits
>>> combined_fruits = *fruits, *more_fruits

# If there is no * on to the left of the "=" the result is a tuple
>>> combined_fruits
("apple", "banana", "cherry", "orange", "kiwi", "melon", "mango")

# If the * operator is used on the left side of "=" the result is a list.
# Note the trailing comma. 
>>> *combined_fruits_too, = *fruits, *more_fruits
>>> combined_fruits_too
['apple', 'banana', 'cherry', 'orange', 'kiwi', 'melon', 'mango']

# A list literal can be used instead, but might not be as readable.
>>> [*combined_fruits_too] = *fruits, *more_fruits
>>> combined_fruits_too
['apple', 'banana', 'cherry', 'orange', 'kiwi', 'melon', 'mango']

Para mais detalhes sobre o uso de * e **, consulta a PEP 3132 e a PEP 448.

Empacotar um dicionário com **

Empacotar um dicionário faz-se com o operador **. Isto empacota todos os pares chave-valor de um dicionário noutro dicionário, ou combina dois dicionários.

>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> more_fruits_inventory = {"orange": 4, "kiwi": 1, "melon": 2, "mango": 3}

# fruits_inventory and more_fruits_inventory are unpacked into key-values pairs and combined.
>>> combined_fruits_inventory = {**fruits_inventory, **more_fruits_inventory}

# then the pairs are packed into combined_fruits_inventory
>>> combined_fruits_inventory
{"apple": 6, "banana": 2, "cherry": 3, "orange": 4, "kiwi": 1, "melon": 2, "mango": 3}

Utilização de * e ** com funções

Empacotar com parâmetros de função

Quando crias uma função que aceita um número arbitrário de argumentos, podes usar *args ou **kwargs na definição da função. *args é usado para empacotar um número arbitrário de argumentos posicionais (não de palavra-chave) e **kwargs é usado para empacotar um número arbitrário de argumentos de palavra-chave.

Utilização de *args:

# This function is defined to take any number of positional arguments

>>> def my_function(*args):
...   print(args)

# Arguments given to the function are packed into a tuple

>>> my_function(1, 2, 3)
(1, 2, 3)

>>> my_function("Hello")
("Hello")

>>> my_function(1, 2, 3, "Hello", "Mars")
(1, 2, 3, "Hello", "Mars")

Utilização de **kwargs:

# This function is defined to take any number of keyword arguments

>>> def my_function(**kwargs):
...   print(kwargs)

# Arguments given to the function are packed into a dictionary

>>> my_function(a=1, b=2, c=3)
{"a": 1, "b": 2, "c": 3}

*args e **kwargs também podem ser usados em conjunto um com o outro:

>>> def my_function(*args, **kwargs):
...   print(sum(args))
...   for key, value in kwargs.items():
...       print(str(key) + " = " + str(value))

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

Também podes escrever parâmetros antes de *args para permitir argumentos posicionais específicos. Os argumentos de palavra-chave individuais têm então de aparecer antes de **kwargs.

Caution

Os argumentos têm de ser estruturados assim:

def my_function(<positional_args>, *args, <key-word_args>, **kwargs)

Se não seguires esta ordem, vais obter um erro.

>>> def my_function(a, b, *args):
...   print(a)
...   print(b)
...   print(args)

>>> my_function(1, 2, 3, 4, 5)
1
2
(3, 4, 5)

Escrever os argumentos numa ordem incorreta resulta num erro:

>>> def my_function(*args, a, b):
...   print(args)

>>>my_function(1, 2, 3, 4, 5)
Traceback (most recent call last):
  File "c:\something.py", line 3, in <module>
    my_function(1, 2, 3, 4, 5)
TypeError: my_function() missing 2 required keyword-only arguments: 'a' and 'b'

Desempacotar em chamadas de função

Podes usar * para desempacotar uma list/tuple de argumentos numa chamada de função. Isto é muito útil para funções que não aceitam um iterable:

>>> def my_function(a, b, c):
...   print(c)
...   print(b)
...   print(a)

numbers = [1, 2, 3]
>>> my_function(*numbers)
3
2
1

Usar o desempacotamento com * em conjunto com a função incorporada zip() é outro caso de uso comum. A função zip() recebe vários iteráveis e devolve uma list de tuples com os valores de cada iterable agrupados:

>>> values = (['x', 'y', 'z'], [1, 2, 3], [True, False, True])
>>> a, *rest = zip(*values)
>>> rest
[('y', 2, False), ('z', 3, True)]
Editar via GitHub A ligação abre numa nova janela ou separador

Aprende Desempacotamento e atribuição múltipla

A prática está bloqueada

Desbloqueia mais 3 exercícios para praticares Desempacotamento e atribuição múltipla