L'unpacking è l'atto di estrarre gli elementi di una collezione, come una list, una tuple o un dict, usando l'iterazione.
I valori così ottenuti possono poi essere assegnati a delle variabili all'interno della stessa istruzione: è quello che comunemente si chiama assegnazione multipla.
Gli operatori speciali * e ** sono spesso usati in contesti di unpacking e con l'assegnazione multipla.
*<variable_name> e **<variable_name> non vanno confusi con * e **. Mentre * e ** sono usati rispettivamente per la moltiplicazione e l'elevamento a potenza, *<variable_name> e **<variable_name> sono usati come operatori di packing e unpacking.
Nell'assegnazione multipla, il numero di variabili a sinistra dell'operatore di assegnazione (=) deve corrispondere al numero di valori a destra.
Per separare i valori, usa una virgola ,:
>>> a, b = 1, 2
>>> a
1
Se nell'assegnazione multipla il numero di variabili non corrisponde ai valori forniti, viene sollevato un ValueError:
>>> x, y, z = 1, 2
ValueError: too many values to unpack (expected 3, got 2)
L'assegnazione multipla non è limitata a un solo tipo di dato:
>>> x, y, z = 1, "Hello", True
>>> x
1
>>> y
'Hello'
>>> z
True
L'assegnazione multipla può essere usata per scambiare gli elementi nelle lists.
Questa pratica è piuttosto comune negli algoritmi di ordinamento.
Per esempio:
>>> numbers = [1, 2]
>>> numbers[0], numbers[1] = numbers[1], numbers[0]
>>> numbers
[2, 1]
Dato che le tuples sono immutabili, non puoi scambiare gli elementi di una tuple.
Gli esempi qui sotto usano le lists, ma gli stessi concetti valgono per le tuples.
In Python, è possibile eseguire l'unpacking degli elementi di list/tuple/dictionary in variabili distinte.
Dato che i valori compaiono all'interno di lists/tuples in un ordine specifico, vengono assegnati alle variabili nello stesso ordine:
>>> fruits = ["apple", "banana", "cherry"]
>>> x, y, z = fruits
>>> x
"apple"
Se ci sono valori che non ti servono, puoi usare _ per contrassegnarli:
>>> fruits = ["apple", "banana", "cherry"]
>>> _, _, z = fruits
>>> z
"cherry"
L'unpacking e l'assegnazione di valori da una list/tuple racchiusa in un'altra list o tuple (note anche come liste/tuple annidate) funziona allo stesso modo di un unpacking superficiale, ma spesso richiede dei qualificatori per chiarire il contesto o la posizione:
>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>>> [[a, b], [c, d]] = fruits_vegetables
>>> a
"apple"
>>> d
"potato"
Puoi anche eseguire un unpacking profondo solo di una parte di una list/tuple annidata:
>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>>> [a, [c, d]] = fruits_vegetables
>>> a
["apple", "banana"]
>>> c
"carrot"
Se nell'unpacking le variabili hanno una posizione errata e/o un numero di valori errato, otterrai un ValueError:
>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>>> [[a, b], [d]] = fruits_vegetables
ValueError: too many values to unpack (expected 1)
*
Quando esegui l'unpacking di una list/tuple, puoi usare l'operatore * per catturare i valori «rimanenti».
Questo è più chiaro dello slicing della list/tuple (che in alcune situazioni è meno leggibile).
Per esempio, si può estrarre il primo elemento e poi inserire i valori rimanenti in una nuova list senza il primo elemento:
>>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>>> x, *last = fruits
>>> x
"apple"
>>> last
["banana", "cherry", "orange", "kiwi", "melon", "mango"]
Possiamo anche estrarre i valori all'inizio e alla fine della list, raggruppando tutti i valori nel mezzo:
>>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>>> x, *middle, y, z = fruits
>>> y
"melon"
>>> middle
["banana", "cherry", "orange", "kiwi"]
Possiamo anche usare * nell'unpacking profondo:
>>> fruits_vegetables = [["apple", "banana", "melon"], ["carrot", "potato", "tomato"]]
>>> [[a, *rest], b] = fruits_vegetables
>>> a
"apple"
>>> rest
["banana", "melon"]
Eseguire l'unpacking di un dictionary è un po' diverso dall'unpacking di una list/tuple.
L'iterazione sui dictionary restituisce di default le chiavi.
Quindi, quando esegui l'unpacking di un dict, puoi estrarre solo le chiavi e non i valori:
>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> x, y, z = fruits_inventory
>>> x
"apple"
Se vuoi estrarre i valori, puoi usare il metodo <dict>.values():
>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> x, y, z = fruits_inventory.values()
>>> x
6
Se servono sia le chiavi sia i valori, usa il metodo <dict>.items().
<dict>.items() genera una vista iterabile contenente coppie chiave-valore.
Queste possono essere estratte in una tuple:
>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> x, y, z = fruits_inventory.items()
>>> x
("apple", 6)
Il packing è la capacità di raggruppare più valori in un'unica list assegnata a una variabile.
È utile quando vuoi eseguire l'unpacking dei valori, apportare delle modifiche e poi eseguire il packing dei risultati di nuovo in una variabile.
Rende anche possibile unire 2 o più lists/tuples/dicts.
*
Il packing di una list/tuple si può fare usando l'operatore *.
In questo modo tutti i valori vengono raggruppati in una 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']
Per maggiori dettagli sull'uso di * sul lato sinistro, vedi la PEP 3132.
**
Il packing di un dictionary si fa usando l'operatore **.
In questo modo tutte le coppie chiave-valore di un dictionary vengono raggruppate in un altro dictionary, oppure due dictionary vengono combinati insieme.
>>> 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}
* e ** con le funzioniQuando crei una funzione che accetta un numero arbitrario di argomenti, puoi usare *args o **kwargs nella definizione della funzione.
*args serve a raggruppare un numero arbitrario di argomenti posizionali (non keyword) in una tuple, mentre **kwargs serve a raggruppare un numero arbitrario di argomenti keyword in un dictionary.
Uso di *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")
Uso di **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 possono anche essere usati in combinazione tra loro:
>>> 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
Puoi anche scrivere dei parametri prima di *args per consentire argomenti posizionali specifici.
I singoli argomenti keyword devono poi comparire prima di **kwargs.
Gli argomenti devono essere strutturati così:
def my_function(<positional_args>, *args, <key-word_args>, **kwargs)
Se non rispetti questo ordine, otterrai un errore.
>>> def my_function(a, b, *args):
... print(a)
... print(b)
... print(args)
>>> my_function(1, 2, 3, 4, 5)
1
2
(3, 4, 5)
Scrivere gli argomenti in un ordine errato produce un errore:
>>>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'
Puoi usare * per eseguire l'unpacking di una list/tuple di argomenti in una chiamata di funzione.
È molto utile per le funzioni che non accettano un iterable:
>>> def my_function(a, b, c):
... print(c)
... print(b)
... print(a)
numbers = [1, 2, 3]
>>> my_function(*numbers)
3
2
1
Usare l'unpacking con * insieme alla funzione integrata zip() è un altro caso d'uso comune.
La funzione zip() prende più iterable e restituisce una list di tuples con i valori di ogni iterable raggruppati:
>>> values = (['x', 'y', 'z'], [1, 2, 3], [True, False, True])
>>> a, *rest = zip(*values)
>>> rest
[('y', 2, False), ('z', 3, True)]
Il tuo amico Linus è un macchinista che guida treni merci tra le città. Anche se è bravissimo con i treni, non è altrettanto bravo con la logistica o con i computer. Vorrebbe il tuo aiuto con la programmazione per organizzare i dettagli dei treni e correggere gli errori nei dati dei percorsi.
Questo esercizio si potrebbe risolvere facilmente con lo slicing, l'indicizzazione e vari metodi dei dict.
Tuttavia, vorremmo che ti esercitassi con il packing, l'unpacking e l'assegnazione multipla nel risolvere ciascuno dei compiti qui sotto.
Il tuo amico tiene traccia da tempo dell'identificativo (ID) di ogni vagone, ma non è mai sicuro di quanti vagoni il sistema dovrà elaborare in un dato momento.
Sarebbe molto più semplice, per il resto del programma di logistica, avere questi dati raggruppati in un unico list.
Implementa una funzione get_list_of_wagons() che accetta un numero arbitrario di ID di vagoni.
Ogni ID sarà un numero intero positivo.
La funzione deve poi return gli ID ricevuti sotto forma di un unico list.
>>> get_list_of_wagons(1, 7, 12, 3, 14, 8, 5)
[1, 7, 12, 3, 14, 8, 5]
A questo punto, stai iniziando a prendere confidenza con i dati e con il modo in cui vengono usati nel programma di logistica. Il sistema degli ID assegna sempre alla locomotiva un ID pari a 1, mentre agli altri vagoni del treno viene assegnato un ID scelto a caso maggiore di 1.
Il tuo amico ha dovuto agganciare due nuovi vagoni al treno e si è dimenticato di aggiornare il sistema!
Ora, i primi due vagoni della list del treno devono essere spostati alla fine, altrimenti tutto andrà fuori ordine.
Per complicare ulteriormente le cose, il tuo amico ha appena scoperto una seconda list che sembra contenere gli ID dei vagoni mancanti.
Tutto ciò che ricorda è che, una volta spostati i nuovi vagoni, gli ID di questa seconda list devono essere posizionati subito dopo la locomotiva designata.
Linus ti sarebbe davvero grato se correggessi i suoi errori e consolidassi i dati.
Implementa una funzione fix_list_of_wagons() che accetta due lists contenenti gli ID dei vagoni.
Deve spostare i primi due elementi della prima list alla fine e inserire i valori della seconda list dietro (alla destra di) l'ID della locomotiva (1).
La funzione deve poi return un list con le modifiche.
>>> fix_list_of_wagons([2, 5, 1, 7, 4, 12, 6, 3, 13], [3, 17, 6, 15])
[1, 3, 17, 6, 15, 7, 4, 12, 6, 3, 13, 2, 5]
Ora che tutti i dati dei vagoni sono corretti, Linus vorrebbe che aggiornassi le informazioni di instradamento del sistema.
Lungo un percorso di trasporto, un treno può fermarsi in alcune stazioni diverse per caricare e/o scaricare merci.
Ogni viaggio può avere un numero diverso di questi punti di consegna intermedi.
Il tuo amico vorrebbe che aggiornassi il dict di instradamento del sistema con le informazioni di consegna mancanti o aggiuntive.
Implementa una funzione add_missing_stops() che accetta un dict di instradamento seguito da un numero variabile di argomenti keyword.
Questi argomenti possono essere sotto forma di un dict che contiene una o più fermate, oppure un numero qualsiasi di coppie stop_number=city.
La funzione deve poi restituire il dict di instradamento aggiornato con una key aggiuntiva che contiene un list con tutte le fermate aggiunte, in ordine.
>>> add_missing_stops({"from": "New York", "to": "Miami"},
stop_1="Washington, DC", stop_2="Charlotte", stop_3="Atlanta",
stop_4="Jacksonville", stop_5="Orlando")
{"from": "New York", "to": "Miami", "stops": ["Washington, DC", "Charlotte", "Atlanta", "Jacksonville", "Orlando"]}
Linus ha lavorato al programma di instradamento e ha notato che ad alcuni percorsi mancano dettagli importanti.
Le informazioni iniziali sul percorso sono state costruite come un dict e il tuo amico vorrebbe che aggiornassi quel dict con tutto ciò che potrebbe mancare.
Ogni percorso nel sistema richiede dettagli leggermente diversi, quindi Linus preferirebbe davvero una soluzione generica.
Implementa una funzione chiamata extend_route_information() che accetta due dicts.
Il primo dict contiene le città di partenza e di destinazione tra cui si svolge il percorso del treno.
Il secondo dict contiene altri dettagli di instradamento, come la velocità, la lunghezza o la temperatura del treno.
La funzione deve restituire un dict consolidato con tutte le informazioni di instradamento.
Il secondo dict può contenere proprietà diverse o in numero maggiore rispetto a quelle mostrate nell'esempio.
>>> extend_route_information({"from": "Berlin", "to": "Hamburg"}, {"length": "100", "speed": "50"})
{"from": "Berlin", "to": "Hamburg", "length": "100", "speed": "50"}
Quando Linus stava ispezionando il deposito dei vagoni, si è accorto che i vagoni non venivano immagazzinati nell'ordine corretto. Oltre a un ID, ogni vagone ha un colore che corrisponde al tipo di merce che trasporta. I vagoni vengono immagazzinati nel deposito in griglie, dove ogni colonna della griglia contiene vagoni dello stesso colore.
Tuttavia, il sistema di logistica mostra che le lists di vagoni da immagazzinare nel deposito hanno le righe raggruppate per colore.
Ma perché la griglia di stoccaggio funzioni correttamente, ogni riga deve avere tre colori diversi, così che le colonne si allineino per colore.
Il tuo amico vorrebbe che sistemassi le lists del deposito dei vagoni, così che i vagoni vengano immagazzinati correttamente.
Implementa una funzione chiamata fix_wagon_depot() che accetta un list di tre elementi.
Ogni elemento del list è una sottolista (o «riga») che contiene tre tuples.
Ogni tuple è una coppia (<wagon ID>, <wagon color>).
La funzione deve restituire un list con le tre lists «riga» riordinate in modo che i vagoni vengano scambiati nelle posizioni corrette.
>>> fix_wagon_depot([
[(2, "red"), (4, "red"), (8, "red")],
[(5, "blue"), (9, "blue"), (13,"blue")],
[(3, "orange"), (7, "orange"), (11, "orange")],
])
[
[(2, "red"), (5, "blue"), (3, "orange")],
[(4, "red"), (9, "blue"), (7, "orange")],
[(8, "red"), (13,"blue"), (11, "orange")]
]
Iscriviti a Exercism per imparare e padroneggiare Python con 17 concetti146 esercizi e il mentoring di persone reali, tutto gratis.