Desempacotando o Poder do Python: Domine o Asterisco (*) e (**) Agora!

Desvendando o Poder do Desempacotamento em Python

Python, uma das linguagens de programação mais populares, oferece recursos poderosos, e um dos mais úteis e, por vezes, subestimados, é o desempacotamento. Neste artigo, vamos explorar este conceito essencial e aprender como usá-lo para escrever código Python mais elegante e eficiente.

Se você já se deparou com os símbolos * e ** em códigos de outras pessoas, ou mesmo os utilizou sem entender completamente seu propósito, este guia é para você. Vamos desmistificar o desempacotamento e mostrar como ele pode aprimorar suas habilidades de programação em Python.

Para melhor acompanhar este tutorial, é importante ter familiaridade com os seguintes conceitos:

  • Iterável: Qualquer sequência de dados que pode ser percorrida em um loop, como listas, tuplas, conjuntos e dicionários.
  • Callable: Um objeto Python que pode ser executado usando parênteses, como uma função (ex: minha_funcao()).
  • Shell: Um ambiente de execução interativo que permite a execução de código Python diretamente. Pode ser acessado digitando “python” em um terminal.
  • Variável: Um nome simbólico que armazena um objeto e ocupa um espaço de memória reservado.

É comum a confusão inicial com o uso de asteriscos em Python, pois eles também funcionam como operadores aritméticos. Um único asterisco (*) é usado para multiplicação, enquanto dois asteriscos (**) indicam exponenciação.

>>> 3*3
9
>>> 3**3
27

Para confirmar, experimente abrir um shell Python e digitar os exemplos acima.

Observação: Certifique-se de ter o Python 3 instalado para acompanhar este tutorial. Caso precise, consulte nosso guia de instalação do Python.

Como pode ver, o asterisco está entre os dois números, indicando o uso como operadores aritméticos.

>>> *range(1, 6),
(1, 2, 3, 4, 5)
>>> {**{'vanilla':3, 'chocolate':2}, 'strawberry':2}
{'vanilla': 3, 'chocolate': 2, 'strawberry': 2}

Por outro lado, quando os asteriscos (* e **) são usados antes de um iterável, eles servem para desempacotá-lo, como demonstrado nos exemplos acima.

Não se preocupe se não entender tudo agora, este é só o começo do nosso guia sobre desempacotamento em Python. Siga em frente e explore todos os detalhes!

O que é Desempacotar?

Desempacotar é o processo de extrair elementos de iteráveis como listas, tuplas e dicionários. Imagine abrir uma caixa e retirar diversos itens, como cabos, fones de ouvido e um pendrive. Essa analogia se aplica ao desempacotamento em Python.

>>> minha_caixa = ['cabos', 'fones', 'pendrive']
>>> item1, item2, item3 = minha_caixa

Vamos examinar este exemplo para entender melhor o conceito:

Neste caso, atribuímos os três elementos da lista minha_caixa às três variáveis item1, item2 e item3, o que demonstra a essência do desempacotamento em Python.

>>> item1
'cabos'
>>> item2
'fones'
>>> item3
'pendrive'

Ao verificarmos os valores de cada variável, vemos que item1 corresponde a ‘cabos’, item2 a ‘fones’ e assim por diante.

>>> nova_caixa = ['cabos', 'fones', 'pendrive', 'mouse']
>>> item1, item2, item3 = nova_caixa
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: too many values to unpack (expected 3)

Até agora, tudo parece funcionar perfeitamente. Mas, o que aconteceria se tentássemos desempacotar uma lista com mais elementos do que variáveis atribuídas?

Como esperado, este código gera um erro. Basicamente, estamos tentando atribuir 4 elementos de uma lista a apenas 3 variáveis. Como o Python faria a correspondência correta? Surge um ValueError, com a mensagem “muitos valores para desempacotar”. Isso ocorre porque há uma incompatibilidade entre o número de variáveis à esquerda e o número de valores (correspondentes à lista nova_caixa) à direita.

>>> ultima_caixa = ['cabos', 'fones']
>>> item1, item2, item3 = ultima_caixa
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: not enough values to unpack (expected 3, got 2)

Ao tentar um processo similar, mas com mais variáveis do que valores para desempacotar, você receberá outro ValueError, com uma mensagem ligeiramente diferente:

Observação: Embora estejamos trabalhando com listas, esta forma de desempacotamento funciona com qualquer iterável (listas, conjuntos, tuplas, dicionários).

Como podemos lidar com essa situação? Existe alguma maneira de desempacotar todos os itens de um iterável para algumas variáveis sem gerar erros?

A resposta é sim! Usaremos o operador de desempacotamento, ou operador de asterisco (*, **). Vamos explorar como utilizá-lo em Python.

Desempacotando Listas com o Operador *

O operador asterisco (*)

>>> primeiro, *nao_utilizados, ultimo = [1, 2, 3, 5, 7]
>>> primeiro
1
>>> ultimo
7
>>> nao_utilizados
[2, 3, 5]

é usado para desempacotar os valores de um iterável que ainda não foram atribuídos.

>>> primeiro, *_, ultimo = [1, 2, 3, 5, 7]
>>> _
[2, 3, 5]

Suponha que queiramos obter o primeiro e o último elemento de uma lista sem usar índices. Podemos fazer isso com o operador asterisco:

>>> primeiro, *_, ultimo = [1, 2]
>>> primeiro
1
>>> ultimo
2
>>> _
[]

Como podemos ver, todos os valores não utilizados são capturados pelo operador asterisco. A prática recomendada para descartar valores é usando a variável sublinhada (_), que é frequentemente usada como uma variável “dummy”.

Podemos usar essa técnica mesmo quando a lista contém apenas dois elementos:

Nesse caso, a variável sublinhada (_) (variável dummy) armazena uma lista vazia, permitindo que as outras duas variáveis acessem os valores disponíveis da lista.

>>> *string = 'PythonEhOMelhor'

Solução de Problemas Comuns

>>> *string = 'PythonEhOMelhor'
  File "<stdin>", line 1
SyntaxError: starred assignment target must be in a list or tuple

É possível desempacotar um único elemento de um iterável. Por exemplo, poderíamos tentar algo como: Entretanto, o código acima retorna um SyntaxError, pois, de acordo com a

especificação PEP

>>> *string, = 'PythonEhOMelhor'
>>> string
['P', 'y', 't', 'h', 'o', 'n', 'E', 'h', 'O', 'M', 'e', 'l', 'h', 'o', 'r']

, no lado esquerdo de uma atribuição, deve-se ter uma tupla (ou lista).

>>> *numeros, = range(5)
>>> numeros
[0, 1, 2, 3, 4]

Se quisermos desempacotar todos os valores de um iterável para uma única variável, devemos usar uma tupla. Adicionar uma vírgula simples é suficiente:

Outro exemplo seria usar a função range, que retorna uma sequência de números.

Agora que sabemos como desempacotar listas e tuplas com um asterisco, vamos explorar como desempacotar dicionários.

Desempacotando Dicionários com o Operador **

>>> **saudacoes, = {'ola': 'OLA', 'tchau':'TCHAU'} 
...
SyntaxError: invalid syntax

Enquanto um asterisco (*) é usado para desempacotar listas e tuplas, dois asteriscos (**) são usados para desempacotar dicionários.

>>> comida = {'peixe':3, 'carne':5, 'massa':9} 
>>> cores = {'vermelho': 'intensidade', 'amarelo':'alegria'}
>>> dicionario_combinado = {**comida, **cores}
>>> dicionario_combinado
{'peixe': 3, 'carne': 5, 'massa': 9, 'vermelho': 'intensidade', 'amarelo': 'alegria'}

Infelizmente, não podemos desempacotar um dicionário em uma única variável da mesma forma que fizemos com tuplas e listas. Portanto, o seguinte código irá gerar um erro:

No entanto, podemos usar o operador ** dentro de callables e outros dicionários. Por exemplo, se quisermos criar um dicionário combinado, a partir de outros dicionários, podemos usar o seguinte código:

Esta é uma maneira concisa de criar dicionários combinados, mas não é a aplicação principal do desempacotamento em Python.

Agora, vamos ver como usar o desempacotamento com callables.

Desempacotamento em Funções: args e kwargs

Você provavelmente já viu args e kwargs implementados em classes ou funções. Vamos entender por que precisamos usá-los em conjunto com callables.

>>> def produto(n1, n2):
...     return n1 * n2
... 
>>> numeros = [12, 1]
>>> produto(*numeros)
12

Desempacotando com o Operador * (args)

>>> produto(12, 1)
12

Suponha que temos uma função que calcula o produto de dois números.

>>> numeros = [12, 1, 3, 4]
>>> produto(*numeros)
...
TypeError: produto() takes 2 positional arguments but 4 were given

Como você pode ver, estamos desempacotando os números da lista para a função, então, na verdade, estamos executando:

>>> def produto(*args):
...     resultado = 1
...     for i in args:
...             resultado *= i
...     return resultado
...
>>> produto(*numeros)
144

Até aqui, tudo funciona perfeitamente, mas o que aconteceria se quiséssemos passar uma lista mais longa? Isso certamente geraria um erro, pois a função estaria recebendo mais argumentos do que consegue lidar.

Podemos resolver esse problema desempacotando a lista diretamente na função, o que cria um iterável dentro dela e nos permite passar qualquer número de argumentos para a função.

Aqui, tratamos o parâmetro args como um iterável, percorrendo seus elementos e retornando o produto de todos os números. Observe que o valor inicial do resultado deve ser 1, pois, se começarmos com zero, a função sempre retornará zero. Observação: args é apenas uma convenção, você pode usar qualquer outro nome de parâmetro. Poderíamos também passar números arbitrários para a função sem usar uma lista, como na função nativa

>>> produto(5, 5, 5)
125
>>> print(5, 5, 5)
5 5 5

print.

>>> def testar_tipo(*args):
...     print(type(args))
...     print(args)
... 
>>> testar_tipo(1, 2, 4, 'uma string')
<class 'tuple'>
(1, 2, 4, 'uma string')

.

Por fim, vamos verificar o tipo de objeto dos argumentos de uma função.

Como demonstrado no código acima, o tipo de args sempre será tupla, e seu conteúdo será todos os argumentos sem palavras-chave passados para a função.

Desempacotamento com o Operador ** (kwargs)

>>> def criar_pessoa(nome, **kwargs):
...     resultado = nome + ': '
...     for chave, valor in kwargs.items():
...             resultado += f'{chave} = {valor}, '
...     return resultado
... 
>>> criar_pessoa('Melissa', id=12112, local='londres', patrimônio=12000)
'Melissa: id = 12112, local = londres, patrimônio = 12000, '

Como vimos antes, o operador ** é usado exclusivamente para dicionários. Isso significa que, com esse operador, podemos passar pares chave-valor para a função como parâmetro.

Vamos criar uma função criar_pessoa, que recebe um argumento posicional “nome” e uma quantidade indefinida de argumentos com palavras-chave.

Como você pode ver, a instrução **kwargs converte todos os argumentos com palavras-chave em um dicionário, que pode ser iterado dentro da função.

>>> def testar_kwargs(**kwargs):
...     print(type(kwargs))
...     print(kwargs)
... 
>>> testar_kwargs(aleatorio=12, parametros=21)
<class 'dict'>
{'aleatorio': 12, 'parametros': 21}

Observação: kwargs é apenas uma convenção, você pode nomear esse parâmetro como quiser.

Podemos verificar o tipo de kwargs da mesma forma que fizemos com args:

>>> def minha_funcao_final(*args, **kwargs):
...     print('Tipo args: ', type(args))
...     print('args: ', args)
...     print('Tipo kwargs: ', type(kwargs))
...     print('kwargs: ', kwargs)
... 
>>> minha_funcao_final('Python', 'O', 'Melhor', linguagem="Python", usuarios="Muitos")
Type args:  <class 'tuple'>
args:  ('Python', 'O', 'Melhor')
Type kwargs:  <class 'dict'>
kwargs:  {'linguagem': 'Python', 'usuarios': 'Muitos'}

A variável interna do kwargs sempre se transforma em um dicionário, que armazena os pares chave-valor passados para a função.

Finalmente, vamos usar args e kwargs na mesma função:

Conclusão

  • Os operadores de desempacotamento são muito úteis em tarefas do dia-a-dia. Agora você sabe como usá-los tanto em instruções individuais quanto em parâmetros de função.
  • Neste tutorial você aprendeu:
  • Use * para tuplas e listas, e ** para dicionários.
  • Você pode usar operadores de desempacotamento em construtores de funções e classes.

args são usados para passar parâmetros sem palavras-chave para funções. kwargs são usados para passar parâmetros com palavras-chave para funções.