Come utilizzare gli operatori di disimballaggio (*, **) in Python?

Python è il linguaggio di programmazione più utilizzato. Oggi imparerai a utilizzare una delle sue funzionalità principali, ma spesso ignorate, decomprimendole in Python.

Probabilmente hai visto * e ** nel codice di altri o addirittura li hai usati senza sapere realmente quale sia il loro scopo. Esamineremo il concetto di spacchettamento e come usarlo per scrivere più codice Pythonic.

Ecco un elenco di concetti che troverai utili durante la lettura di questo tutorial:

  • Iterabile: qualsiasi sequenza che può essere ripetuta da un ciclo for, come insiemi, elenchi, tuple e dizionari
  • Callable: un oggetto Python che può essere chiamato utilizzando le doppie parentesi (), ad esempio myfunction()
  • Shell: ambiente di runtime interattivo che ci consente di eseguire codice Python. Possiamo chiamarlo eseguendo “python” in un terminale
  • Variabile: nome simbolico che memorizza un oggetto e ha una posizione di memoria riservata.

Cominciamo con la confusione più frequente: gli asteristici in Python sono anche operatori aritmetici. Un asterisco

viene utilizzato per la moltiplicazione, mentre due di essi (**) si riferiscono all’elevamento a potenza.

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

Possiamo dimostrarlo aprendo una shell Python e digitando:

Nota: devi avere installato Python 3 per seguire questo tutorial. Se non lo hai installato, dai un’occhiata alla nostra guida all’installazione di Python.

Come puoi vedere, stiamo usando l’asterisco dopo il primo numero e prima del secondo. Quando vedi questo, significa che stiamo usando gli operatori aritmetici.

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

D’altra parte, usiamo gli asterischi (*, **) prima di un iterabile per decomprimerlo, ad esempio:

Non preoccuparti se non lo capisci, questo è solo un preambolo per decomprimere in Python. Quindi vai avanti e leggi l’intero tutorial!

Cosa sta disimballando?

Lo spacchettamento è il processo di estrazione di cose: iterabili come liste, tuple e dizionari. Pensalo come aprire una scatola ed estrarre diversi oggetti come cavi, cuffie o USB.

Il disimballaggio in Python è simile al disimballaggio di una scatola nella vita reale.

>>> mybox = ['cables', 'headphones', 'USB']
>>> item1, item2, item3 = mybox

Traduciamo questo stesso esempio in codice per una migliore comprensione:

Come puoi vedere, stiamo assegnando i tre elementi all’interno dell’elenco mybox a tre variabili item1, item2, item2. Questo tipo di assegnazione variabile è il concetto fondamentale di spacchettamento in Python.

>>> item1
'cables'
>>> item2
'headphones'
>>> item3
'USB'

Se provi a ottenere il valore di ogni articolo, noterai che l’articolo 1 si riferisce a “cavi”, l’articolo 2 si riferisce a “cuffie” e così via.

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

Fino a qui, tutto sembra andare bene con questo codice, ma se volessimo decomprimere un elenco con più elementi al suo interno, mantenendo la stessa quantità di variabili assegnate?

Probabilmente ti aspettavi questo tipo di errore. In sostanza stiamo assegnando 4 voci di elenco a tre variabili, come riesce Python ad assegnare i valori giusti? Non è così, perché otteniamo un ValoreErrore

con il messaggio “troppi valori da decomprimere”. Questo sta accadendo perché stiamo impostando tre variabili a sinistra e quattro valori (corrispondenti all’elenco newbox) a destra.

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

Se provi a eseguire un processo simile, ma con più variabili che valori da decomprimere, otterrai un altro ValueError tranne quello con un messaggio leggermente diverso:

Nota: abbiamo lavorato con le liste ma puoi usare questa forma di spacchettamento con qualsiasi iterabile (liste, set, tuple, dizionari)

Allora come superiamo questa situazione? C’è un modo per decomprimere tutti gli elementi di un iterabile in un paio di variabili senza ottenere errori?

Certo che c’è, e si chiama operatore di disimballaggio o operatore di asterisco (*, **). Vediamo come usarlo in Python.

Come decomprimere le liste con l’operatore *

L’operatore asterisco

>>> first, *unused, last = [1, 2, 3, 5, 7]
>>> first
1
>>> last
7
>>> unused
[2, 3, 5]

viene utilizzato per decomprimere tutti i valori di un iterabile che non sono stati ancora assegnati.

>>> first, *_, last = [1, 2, 3, 5, 7]
>>> _
[2, 3, 5]

Supponiamo di voler ottenere il primo e l’ultimo elemento di una lista senza utilizzare gli indici, potremmo farlo con l’operatore asterisco:

>>> first, *_, last = [1, 2]
>>> first
1
>>> last
2
>>> _
[]

Come puoi apprezzare, otteniamo tutti i valori inutilizzati con l’operatore asterisco. Il modo preferito per scartare i valori è utilizzare una variabile di sottolineatura (_), che a volte viene utilizzata come “variabile fittizia”.

Possiamo ancora usare questo trucco anche se l’elenco ha solo due elementi:

In questo caso, la variabile underscore (variabile fittizia) memorizza un elenco vuoto in modo che le altre due variabili intorno a loro possano accedere ai valori disponibili dell’elenco.

>>> *string = 'PythonIsTheBest'

Risoluzione dei problemi comuni

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

Possiamo decomprimere un elemento unico di un iterabile. Ad esempio, ti verrebbe in mente qualcosa del genere: Tuttavia, il codice precedente restituirà un SyntaxError:Questo perché secondo l’art

Specifica PEP

:

>>> *string, = 'PythonIsTheBest'
>>> string
['P', 'y', 't', 'h', 'o', 'n', 'I', 's', 'T', 'h', 'e', 'B', 'e', 's', 't']

Una tupla (o lista) sul lato sinistro di un compito semplice

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

Se vogliamo decomprimere tutti i valori di un iterabile in una singola variabile, dobbiamo impostare una tupla, quindi sarà sufficiente aggiungere una semplice virgola:

Un altro esempio potrebbe essere l’utilizzo della funzione range, che restituisce una sequenza di numeri.

Ora che sai come decomprimere liste e tuple con un asterisco, è il momento di passare alla decompressione dei dizionari.

Come decomprimere i dizionari con l’operatore **

>>> **greetings, = {'hello': 'HELLO', 'bye':'BYE'} 
...
SyntaxError: invalid syntax

Mentre un singolo asterisco viene utilizzato per decomprimere elenchi e tuple, il doppio asterisco (**) viene utilizzato per decomprimere i dizionari.

>>> food = {'fish':3, 'meat':5, 'pasta':9} 
>>> colors = {'red': 'intensity', 'yellow':'happiness'}
>>> merged_dict = {**food, **colors}
>>> merged_dict
{'fish': 3, 'meat': 5, 'pasta': 9, 'red': 'intensity', 'yellow': 'happiness'}

Sfortunatamente, non possiamo decomprimere un dizionario in una singola variabile come abbiamo fatto con tuple e liste. Ciò significa che quanto segue genererà un errore:

Tuttavia, possiamo utilizzare l’operatore ** all’interno di callable e altri dizionari. Ad esempio, se vogliamo creare un dizionario unito, composto da altri dizionari, potremmo utilizzare il codice seguente:

Questo è un modo piuttosto breve per creare dizionari composti, tuttavia, questo non è l’approccio principale di decompressione in Python.

Vediamo come possiamo usare la decompressione con i callable

Imballaggio in funzioni: args e kwargs

Probabilmente hai visto args e kwargs prima di implementarli su classi o funzioni. Vediamo perché dobbiamo usarli insieme ai callable.

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

Packing con l’operatore * (args)

>>> product(12, 1)
12

Supponiamo di avere una funzione che calcola il prodotto di due numeri.

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

Come puoi vedere, stiamo decomprimendo i numeri dell’elenco nella funzione, quindi in realtà stiamo eseguendo quanto segue:

>>> def product(*args):
...     result = 1
...     for i in args:
...             result *= i
...     return result
...
>>> product(*numbers)
144

Fino a qui funziona tutto bene, ma se volessimo passare una lista più lunga? Solleverà sicuramente un errore perché la funzione sta ricevendo più argomenti di quanti ne sia in grado di gestire.

Possiamo risolvere tutto questo semplicemente impacchettando l’elenco direttamente sulla funzione, che crea un iterabile al suo interno e ci consente di passare un numero qualsiasi di argomenti alla funzione.

Qui stiamo trattando il parametro args come un iterabile, attraversando i suoi elementi e restituendo il prodotto di tutti i numeri. Nota come il numero iniziale del risultato deve essere uno perché se iniziamo con zero, la funzione restituirà sempre zero. Nota: args è solo una convenzione, puoi usare qualsiasi altro nome di parametroPotremmo anche passare numeri arbitrari alla funzione senza usare una lista, proprio come con l’integrato

>>> product(5, 5, 5)
125
>>> print(5, 5, 5)
5 5 5

funzione di stampa

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

.

Infine, prendiamo il tipo di oggetto degli argomenti di una funzione.

Come notato nel codice precedente, il tipo di args sarà sempre tuple e il suo contenuto sarà costituito da tutti gli argomenti senza parole chiave passati alla funzione.

Imballaggio con l’operatore ** (kwargs)

>>> def make_person(name, **kwargs):
...     result = name + ': '
...     for key, value in kwargs.items():
...             result += f'{key} = {value}, '
...     return result
... 
>>> make_person('Melissa', id=12112, location='london', net_worth=12000)
'Melissa: id = 12112, location = london, net_worth = 12000, '

Come abbiamo visto in precedenza, l’operatore ** viene utilizzato esclusivamente per i dizionari. Ciò significa che con questo operatore siamo in grado di passare coppie chiave-valore alla funzione come parametro.

Creiamo una funzione make_person, che riceve un argomento posizionale “nome” e una quantità indefinita di argomenti con parole chiave.

Come puoi vedere, l’istruzione **kwargs converte tutti gli argomenti con parole chiave in un dizionario, che possiamo iterare all’interno della funzione.

>>> def test_kwargs(**kwargs):
...     print(type(kwargs))
...     print(kwargs)
... 
>>> test_kwargs(random=12, parameters=21)
<class 'dict'>
{'random': 12, 'parameters': 21}

Nota: kwargs è solo una convenzione, puoi nominare questo parametro con quello che vuoi

Possiamo controllare il tipo di kwargs nello stesso modo in cui lo abbiamo fatto con args:

>>> def my_final_function(*args, **kwargs):
...     print('Type args: ', type(args))
...     print('args: ', args)
...     print('Type kwargs: ', type(kwargs))
...     print('kwargs: ', kwargs)
... 
>>> my_final_function('Python', 'The', 'Best', language="Python", users="A lot")
Type args:  <class 'tuple'>
args:  ('Python', 'The', 'Best')
Type kwargs:  <class 'dict'>
kwargs:  {'language': 'Python', 'users': 'A lot'}

La variabile interna kwargs diventa sempre un dizionario, che memorizza le coppie chiave-valore passate alla funzione.

Infine, usiamo args e kwargs nella stessa funzione:

Conclusione

  • Gli operatori di spacchettamento sono davvero utili nelle attività quotidiane, ora sai come usarli sia nelle singole istruzioni che nei parametri di funzione.
  • In questo tutorial hai imparato:
  • Si usa * per tuple e liste e ** per dizionari
  • È possibile utilizzare gli operatori di spacchettamento nei costruttori di funzioni e classi

args sono usati per passare alle funzioni parametri senza parole chiaveI kwargs sono usati per passare parametri con parole chiave alle funzioni.