ProgrammazioneSviluppatore Fullstack Python

Spiega il meccanismo di funzionamento degli operatori * e ** per lo *spacking* di sequenze e dizionari in Python. Quali sono i dettagli e dove può essere utile?

Supera i colloqui con l'assistente IA Hintsage

Risposta.

Storia della domanda:

Gli operatori * e ** per lo spacking sono stati introdotti in Python da tempo, ma il loro utilizzo si è ampliato con ogni versione (ad esempio, a partire da Python 3.5 è stato aggiunto il supporto per l'unione di più collezioni tramite * e **). Questi operatori rendono il lavoro con le collezioni più flessibile, anche nella trasmissione di argomenti e nella loro raccolta nelle funzioni.

Problema:

Senza lo spacking, quando si lavora con sequenze dinamiche e si trasmettono parametri con un numero variabile di argomenti, è necessario scrivere manualmente cicli e controllare le dimensioni delle collezioni. Gli errori possono facilmente insorgere quando vengono usati in modo errato o quando non si distingue tra le funzioni di * e **.

Soluzione:

L'operatore * è destinato a spacking di sequenze (list, tuple, set), mentre l'operatore ** è utilizzato per spacking di dizionari durante le chiamate alle funzioni o per unire più dizionari. Essi permettono di trasmettere argomenti in modo elegante, effettuare fusioni di collezioni, e trasformare facilmente strutture arbitrari in parametri di funzione.

Esempio di codice:

def foo(a, b, c): print(a, b, c) args = (1, 2, 3) foo(*args) # 1 2 3 params = {'a': 10, 'b': 20, 'c': 30} foo(**params) # 10 20 30 list1 = [1, 2] list2 = [3, 4] combined = [*list1, *list2] print(combined) # [1, 2, 3, 4]

Caratteristiche chiave:

  • *sequence trasmette gli elementi come argomenti posizionali separati, **dict come argomenti nominati.
  • È possibile unire e copiare collezioni in modo elegante tramite [*a, *b] e {**d1, **d2}.
  • Lo spacking è applicabile sia nella definizione/declarazione delle funzioni che durante la chiamata.

Domande insidiose.

È possibile utilizzare * e ** per collezioni miste?

Durante la chiamata di funzione, * funziona solo con argomenti posizionali, mentre ** solo con argomenti nominati. Se si passa un dict non spacked come * o una sequenza come **, si verificherà un errore.

def foo(a, b): print(a, b) foo(*{'a': 1, 'b': 2}) # Stampa: a b (chiavi del dizionario, non valori!)

**Cosa succede se i nomi delle chiavi si sovrappongono durante l'unione dei dict tramite {**d1, d2}?

Il risultato sarà il valore dell'ultimo dizionario con quella chiave.

d1 = {'x': 1, 'y': 2} d2 = {'y': 33, 'z': 44} merged = {**d1, **d2} print(merged) # {'x': 1, 'y': 33, 'z': 44}

È possibile utilizzare * e ** all'interno di espressioni di liste o dizionari?

Sì, ciò è legittimo a partire da Python 3.5, ad esempio:

lst = [1, 2, *range(3, 6)] # [1, 2, 3, 4, 5] dct = {**{'a': 1}, 'b': 2, **{'c': 3}}

Errori comuni e anti-pattern

  • Trasmettere dict tramite * (verranno spacked solo le chiavi).
  • Un eccessivo sovrapposizione di argomenti nominati può causare TypeError.
  • Tentare di utilizzare ** su un oggetto che non è un mapping.

Esempio dalla vita reale

Caso negativo

Un sviluppatore accetta un dict in una funzione e lo analizza usando * invece di **. Si verifica un comportamento inaspettato: nella funzione arrivano le chiavi, non i valori.

Pro:

Il codice non fallisce immediatamente, sembra "funzionante".

Contro:

Errori nascosti e incongruenze nella logica attesa.

Caso positivo

Trasmissione corretta dei parametri tramite **kwargs, fusione accurata dei dizionari, utilizzo di * per unioni dinamiche delle sequenze.

Pro:

Massima flessibilità, concisione del codice, semplicità di refactoring.

Contro:

Con un numero elevato di parametri e collezioni, è importante prestare attenzione ai nomi e all'ordine, altrimenti possono verificarsi errori.