Quando si utilizza un dizionario in Python, è possibile gestire in modo efficiente le coppie chiave-valore, ma quando si trattano strutture dati complesse, spesso è necessario annidare i dizionari. Quando si manipolano dizionari nidificati o multidimensionali, sono necessarie tecniche diverse rispetto alla manipolazione dei dizionari standard. In questo articolo, esploreremo in modo chiaro e comprensibile i concetti base e avanzati per gestire i dizionari nidificati in Python. Ciò ti permetterà di acquisire conoscenze utili per gestire grandi volumi di dati o strutture complesse.
Che cosa sono i dizionari nidificati?
Un dizionario nidificato è una struttura in cui i valori di un dizionario sono a loro volta altri dizionari. Questa struttura è utile per rappresentare dati gerarchici o raggruppare informazioni correlate.
Struttura di base di un dizionario nidificato
Ecco un esempio di un dizionario nidificato tipico:
data = {
"person1": {"name": "Alice", "age": 30},
"person2": {"name": "Bob", "age": 25},
}
In questo caso, data
è il dizionario principale, e per ogni chiave (person1
e person2
), i relativi valori sono dizionari annidati contenenti informazioni sulla persona.
Esempi di utilizzo dei dizionari nidificati
- Gestione delle informazioni degli utenti
È utile quando si vogliono raccogliere informazioni diverse per ogni utente.
users = {
"user1": {"email": "user1@example.com", "is_active": True},
"user2": {"email": "user2@example.com", "is_active": False},
}
- Rappresentazione di dati gerarchici
Può essere utilizzato per rappresentare dati con una struttura gerarchica, come categorie e sottocategorie.
categories = {
"Fruits": {"Citrus": ["Orange", "Lemon"], "Berries": ["Strawberry", "Blueberry"]},
"Vegetables": {"Leafy": ["Spinach", "Lettuce"], "Root": ["Carrot", "Potato"]},
}
I dizionari nidificati sono estremamente versatili e consentono di gestire facilmente dati complessi, ma è necessario fare attenzione quando li si manipola. Nella sezione successiva, esploreremo come accedere ai dati in un dizionario nidificato.
Come accedere ai dizionari nidificati
Per manipolare un dizionario nidificato, è necessario accedere ai valori utilizzando più chiavi. In Python, l’accesso ai valori avviene specificando le chiavi in sequenza.
Metodo di accesso di base
Per accedere ai valori in un dizionario nidificato, si concatenano le chiavi. Nell’esempio sottostante, accediamo al valore del secondo livello di nidificazione.
data = {
"person1": {"name": "Alice", "age": 30},
"person2": {"name": "Bob", "age": 25},
}
# Ottieni il nome di Alice
name = data["person1"]["name"]
print(name) # Output: Alice
# Ottieni l'età di Bob
age = data["person2"]["age"]
print(age) # Output: 25
Attenzione alle chiavi mancanti
Se si tenta di accedere a una chiave che non esiste nel dizionario, verrà sollevato un KeyError
.
# Accesso a una chiave mancante
print(data["person3"]["name"]) # KeyError: 'person3'
Accesso a nidificazioni più profonde
Si può accedere anche ai dizionari annidati più profondi allo stesso modo, ma la leggibilità del codice potrebbe diminuirne a causa della lunghezza del codice stesso.
deep_data = {
"level1": {
"level2": {
"level3": {"key": "value"}
}
}
}
# Accedi al valore "value"
value = deep_data["level1"]["level2"]["level3"]["key"]
print(value) # Output: value
Accesso tramite comprensione del dizionario
Quando è necessario ottenere più valori, è possibile utilizzare la comprensione del dizionario per farlo in modo efficiente.
data = {
"person1": {"name": "Alice", "age": 30},
"person2": {"name": "Bob", "age": 25},
}
# Ottieni i nomi di tutti
names = {key: value["name"] for key, value in data.items()}
print(names) # Output: {'person1': 'Alice', 'person2': 'Bob'}
Nella sezione successiva, vedremo come utilizzare il metodo get()
per accedere ai valori in modo sicuro, prevenendo gli errori causati da chiavi mancanti.
Accesso sicuro con get()
Quando si accede a un dizionario nidificato e la chiave specificata non esiste, è possibile utilizzare il metodo get()
per evitare errori. Questo metodo restituisce un valore predefinito se la chiave non viene trovata, senza sollevare un’eccezione.
Esempio di utilizzo di base
Ecco un esempio di come utilizzare get()
per un accesso sicuro ai valori.
data = {
"person1": {"name": "Alice", "age": 30},
"person2": {"name": "Bob", "age": 25},
}
# Accesso a una chiave esistente
name = data.get("person1", {}).get("name", "Unknown")
print(name) # Output: Alice
# Accesso a una chiave mancante
name = data.get("person3", {}).get("name", "Unknown")
print(name) # Output: Unknown
Con questo metodo, puoi accedere in modo sicuro ai dizionari nidificati utilizzando get()
concatenato.
Impostare un valore predefinito
Il secondo argomento di get()
consente di impostare un valore predefinito. In questo modo, se la chiave non viene trovata, verrà restituito il valore predefinito invece di generare un errore.
# Restituisce un valore predefinito quando la chiave non esiste
age = data.get("person3", {}).get("age", 0)
print(age) # Output: 0
Funzione per facilitare l’accesso a più chiavi
Quando si manipolano frequentemente dizionari nidificati, è utile creare una funzione che semplifichi l’accesso a più chiavi.
def safe_get(dictionary, keys, default=None):
for key in keys:
dictionary = dictionary.get(key, {})
if not isinstance(dictionary, dict):
return default
return dictionary or default
# Esempio di utilizzo
data = {
"level1": {"level2": {"level3": {"key": "value"}}}
}
value = safe_get(data, ["level1", "level2", "level3", "key"], "Not Found")
print(value) # Output: value
# Chiave mancante
missing = safe_get(data, ["level1", "level4", "key"], "Not Found")
print(missing) # Output: Not Found
Gestire i dizionari non esistenti
Se un dizionario nidificato contiene un valore che non è un dizionario, è possibile evitare errori di tipo utilizzando get()
.
data = {
"person1": {"name": "Alice", "age": 30},
"person2": "Dati non validi"
}
# Gestire in modo sicuro i dati che non sono un dizionario
age = data.get("person2", {}).get("age", "Unknown")
print(age) # Output: Unknown
Utilizzando get()
, è possibile ottenere valori in modo sicuro anche in dizionari nidificati, aumentando la robustezza del codice. Nella sezione successiva, esploreremo come generare dinamicamente dizionari nidificati utilizzando defaultdict
.
Creazione di dizionari nidificati con defaultdict
Quando si manipolano dizionari nidificati, inizializzare manualmente le chiavi prima di accedervi può essere scomodo. Utilizzando defaultdict
dal modulo collections
, è possibile creare dinamicamente dizionari nidificati senza preoccuparsi della loro inizializzazione.
Che cos’è defaultdict?
defaultdict
è un dizionario che fornisce un valore predefinito per ogni chiave mancante. Quando si accede a una chiave che non esiste, defaultdict
crea automaticamente un nuovo valore, rendendo più semplice gestire strutture dati nidificate.
Esempio di utilizzo di base
Di seguito un esempio su come utilizzare defaultdict
per creare dizionari nidificati.
from collections import defaultdict
# defaultdict per generare un dizionario nidificato
nested_dict = defaultdict(dict)
# Assegna valori
nested_dict["person1"]["name"] = "Alice"
nested_dict["person1"]["age"] = 30
nested_dict["person2"]["name"] = "Bob"
# Verifica il risultato
print(nested_dict)
# Output: defaultdict(, {'person1': {'name': 'Alice', 'age': 30}, 'person2': {'name': 'Bob'}})
Quando si accede a una chiave mancante, viene generato automaticamente un nuovo dizionario, eliminando la necessità di inizializzare manualmente le chiavi.
Creazione di dizionari multidimensionali
Per creare dizionari con livelli di nidificazione più profondi, è possibile utilizzare defaultdict
annidato.
from collections import defaultdict
# Creazione di un dizionario nidificato multidimensionale
nested_dict = defaultdict(lambda: defaultdict(dict))
# Assegna valori
nested_dict["level1"]["level2"]["key"] = "value"
# Verifica il risultato
print(nested_dict)
# Output: defaultdict( at 0x...>, {'level1': defaultdict(, {'level2': {'key': 'value'}})})
In questo modo, ogni livello viene inizializzato automaticamente, semplificando la scrittura del codice.
Svantaggi di defaultdict
- Compatibilità con dizionari tradizionali
defaultdict
è diverso dai dizionari normali e quindi, quando si passano a funzioni o librerie (ad esempio, il modulojson
), è necessario convertirlo in un dizionario normale.
import json
# Converte in un dizionario normale
normal_dict = dict(nested_dict)
print(json.dumps(normal_dict, indent=2))
- Generazione di chiavi inaspettate
Quando si accede a chiavi inesistenti,defaultdict
crea automaticamente un nuovo dizionario vuoto. Questa caratteristica può essere problematica se non si desidera questa generazione automatica di chiavi.
Esempio d’uso: conteggi e aggregazioni
defaultdict
è utile anche per operazioni di conteggio o aggregazione in dizionari nidificati.
from collections import defaultdict
# Dizionario nidificato per conteggi
counts = defaultdict(lambda: defaultdict(int))
# Conta i dati
data = [("person1", "apple"), ("person1", "banana"), ("person2", "apple")]
for person, fruit in data:
counts[person][fruit] += 1
# Verifica il risultato
print(counts)
# Output: defaultdict( at 0x...>, {'person1': {'apple': 1, 'banana': 1}, 'person2': {'apple': 1}})
Utilizzando defaultdict
, la creazione e gestione di dizionari nidificati diventa più efficiente e il codice risulta più conciso. Nella sezione successiva, esploreremo come inizializzare e manipolare dizionari multidimensionali.
Inizializzazione e manipolazione di dizionari multidimensionali
L’inizializzazione di dizionari multidimensionali può essere effettuata in modo efficiente utilizzando comprensioni di liste o dizionari in Python. Inoltre, comprendere le modalità di manipolazione dopo l’inizializzazione aiuta a gestire strutture dati complesse in modo fluido.
Inizializzazione manuale di un dizionario multidimensionale
Per inizializzare manualmente un dizionario multidimensionale, si creano strutture nidificate.
data = {
"level1": {
"level2": {
"key1": 0,
"key2": 0
}
}
}
# Modifica dei valori
data["level1"]["level2"]["key1"] = 42
print(data)
# Output: {'level1': {'level2': {'key1': 42, 'key2': 0}}}
Inizializzare manualmente è semplice, ma può diventare ridondante con strutture più complesse.
Inizializzazione efficiente con la comprensione del dizionario
Usando la comprensione del dizionario, è possibile inizializzare facilmente dizionari con struttura gerarchica.
# Inizializzazione di un dizionario tridimensionale
data = {
f"level1_{i}": {
f"level2_{j}": {f"key_{k}": 0 for k in range(3)}
for j in range(2)
}
for i in range(2)
}
print(data)
# Output: {'level1_0': {'level2_0': {'key_0': 0, 'key_1': 0, 'key_2': 0}, ...}}
Questa tecnica consente di generare facilmente chiavi e valori dinamici.
Aggiunta dinamica di valori a un dizionario multidimensionale
Per aggiungere nuovi valori dinamicamente, si utilizzano i metodi di base per la manipolazione dei dizionari.
data = {"level1": {}}
# Aggiunta dinamica di valori
data["level1"]["level2"] = {"key1": 42, "key2": 100}
print(data)
# Output: {'level1': {'level2': {'key1': 42, 'key2': 100}}}
Operazioni su tutto un livello del dizionario
Per manipolare tutti gli elementi di un dizionario multidimensionale, si utilizzano cicli o ricorsione.
def print_nested_dict(d, level=0):
for key, value in d.items():
print(" " * level + f"{key}: {value if not isinstance(value, dict) else ''}")
if isinstance(value, dict):
print_nested_dict(value, level + 1)
# Esecuzione dell'esempio
print_nested_dict(data)
# Output:
# level1:
# level2:
# key1: 42
# key2: 100
Conversione dei valori con la comprensione del dizionario
È possibile anche trasformare i valori esistenti in un dizionario multidimensionale.
data = {"level1": {"level2": {"key1": 1, "key2": 2}}}
# Moltiplica i valori per 2
transformed_data = {
k1: {k2: {k3: v * 2 for k3, v in v2.items()} for k2, v2 in v1.items()}
for k1, v1 in data.items()
}
print(transformed_data)
# Output: {'level1': {'level2': {'key1': 2, 'key2': 4}}}
Combinazione di inizializzazione automatica e operazioni
Per inizializzare e manipolare strutture dinamiche in modo semplice, è utile combinare defaultdict
con la comprensione del dizionario.
from collections import defaultdict
# Inizializzazione automatica
data = defaultdict(lambda: defaultdict(lambda: defaultdict(int)))
# Assegnazione di valori
data["level1"]["level2"]["key1"] += 10
data["level1"]["level2"]["key2"] += 20
print(data)
# Output: defaultdict( at 0x...>, {'level1': defaultdict(...)}))
Comprendere i metodi di inizializzazione e manipolazione efficiente semplifica la gestione dei dizionari multidimensionali complessi. Nella sezione successiva, esploreremo in dettaglio l’aggiornamento e l’aggiunta ai dizionari nidificati.
Metodo per aggiornare e aggiungere dizionari annidati
Nei dizionari annidati, è comune aggiornare i valori di una chiave esistente o aggiungere nuove chiavi e valori. In questa sezione, spiegheremo come eseguire efficientemente aggiornamenti e aggiunte.
Aggiornamento del valore
Per aggiornare un valore esistente, si specifica la chiave e si assegna il nuovo valore. Nei dizionari annidati, si naviga nella gerarchia per specificare il valore desiderato.
data = {
"person1": {"name": "Alice", "age": 30},
"person2": {"name": "Bob", "age": 25},
}
# Aggiornamento del valore
data["person1"]["age"] = 31
print(data)
# Output: {'person1': {'name': 'Alice', 'age': 31}, 'person2': {'name': 'Bob', 'age': 25}}
Aggiunta di una nuova chiave e valore
Se si specifica una chiave che non esiste nel dizionario, verranno aggiunti la nuova chiave e il suo valore.
# Aggiunta di una nuova chiave e valore
data["person3"] = {"name": "Charlie", "age": 22}
print(data)
# Output: {'person1': {...}, 'person2': {...}, 'person3': {'name': 'Charlie', 'age': 22}}
Aggiornamento di massa con merge del dizionario
Con Python 3.9 e versioni successive, è possibile unire i dizionari e aggiornarli tutti in una sola volta utilizzando l’operatore |=
.
updates = {"person1": {"age": 32}, "person4": {"name": "Diana", "age": 28}}
# Unione e aggiornamento
data |= updates
print(data)
# Output: {'person1': {'age': 32}, 'person2': {...}, 'person3': {...}, 'person4': {'name': 'Diana', 'age': 28}}
Aggiornamento con controllo della gerarchia
Per aggiornare solo se la chiave esiste, si possono utilizzare le istruzioni if
o il metodo get()
.
if "person2" in data:
data["person2"]["age"] += 1
else:
data["person2"] = {"age": 1}
print(data)
# Output: {'person1': {...}, 'person2': {'name': 'Bob', 'age': 26}, ...}
Aggiunta utilizzando la generazione automatica di dizionari annidati
Per aggiungere nuove chiavi in una profondità maggiore nei dizionari annidati, collections.defaultdict
è molto utile e più efficiente.
from collections import defaultdict
# Inizializzazione automatica
data = defaultdict(lambda: defaultdict(dict))
# Aggiunta in una profondità maggiore
data["person1"]["address"]["city"] = "New York"
data["person1"]["address"]["zip"] = "10001"
print(data)
# Output: defaultdict(, {'person1': {'address': {'city': 'New York', 'zip': '10001'}}})
Funzione di aggiornamento ricorsivo del dizionario
Per aggiornare l’intero dizionario annidato, può essere utile utilizzare una funzione ricorsiva.
def update_nested_dict(original, updates):
for key, value in updates.items():
if isinstance(value, dict) and key in original:
update_nested_dict(original[key], value)
else:
original[key] = value
# Esempio d'uso
data = {"level1": {"level2": {"key1": "value1"}}}
updates = {"level1": {"level2": {"key2": "value2"}, "level3": {"key3": "value3"}}}
update_nested_dict(data, updates)
print(data)
# Output: {'level1': {'level2': {'key1': 'value1', 'key2': 'value2'}, 'level3': {'key3': 'value3'}}}
Aggiornamenti considerando la gestione degli errori
Per evitare errori se una chiave non esiste, è possibile utilizzare la gestione delle eccezioni.
try:
data["person4"]["age"] = 35
except KeyError:
data["person4"] = {"age": 35}
print(data)
# Output: {'person1': {...}, 'person2': {...}, 'person3': {...}, 'person4': {'age': 35}}
Comprendere i metodi per aggiornare e aggiungere dizionari annidati ti permette di gestire i dati in modo più efficiente e flessibile. La prossima sezione tratterà in dettaglio la gestione degli errori.
Gestione degli errori durante l’uso dei dizionari annidati
Quando si lavorano con dizionari annidati, è importante gestire correttamente gli errori più comuni, come l’accesso a chiavi non esistenti o incoerenze nei tipi di dati. In questa sezione, vedremo come affrontare questi problemi.
Errori comuni e cause
- KeyError
Questo errore si verifica quando si cerca di accedere a una chiave che non esiste.
data = {"level1": {"level2": {"key": "value"}}}
print(data["level1"]["level3"]) # KeyError: 'level3'
- TypeError
Si verifica quando si cerca di accedere a un tipo non dizionario all’interno di un dizionario annidato. - AttributeError
Si verifica quando si applicano operazioni da dizionario a un tipo di dati non compatibile.
data = {"level1": "not a dict"}
print(data["level1"]["level2"]) # TypeError: string indices must be integers
Gestione degli errori di base: accesso sicuro con get()
Utilizzando il metodo get()
, è possibile evitare il KeyError
, restituendo un valore predefinito se la chiave non esiste.
data = {"level1": {"level2": {"key": "value"}}}
# Accesso sicuro anche per chiavi non esistenti
value = data.get("level1", {}).get("level3", "default")
print(value) # Output: default
Gestione flessibile degli errori con le eccezioni
Le eccezioni consentono di controllare il comportamento del programma quando si verifica un errore.
data = {"level1": {"level2": {"key": "value"}}}
try:
value = data["level1"]["level3"]["key"]
except KeyError:
value = "default"
except TypeError:
value = "invalid type"
print(value) # Output: default
Gestione degli errori ricorsiva
Per evitare errori quando si gestiscono dizionari profondamente annidati, è possibile creare una funzione generica che li gestisce.
def safe_get(dictionary, keys, default=None):
for key in keys:
try:
dictionary = dictionary[key]
except (KeyError, TypeError):
return default
return dictionary
# Esempio d'uso
data = {"level1": {"level2": {"key": "value"}}}
print(safe_get(data, ["level1", "level2", "key"], "default")) # Output: value
print(safe_get(data, ["level1", "level3", "key"], "default")) # Output: default
Controllo del tipo di dato
Utilizzando isinstance()
, è possibile verificare i tipi di dato e prevenire errori di tipo.
data = {"level1": {"level2": {"key": "value"}}}
if isinstance(data.get("level1", None), dict):
print(data["level1"].get("level2", {}).get("key", "default"))
else:
print("Invalid data structure")
# Output: value
Evitare gli errori con dizionari automaticamente inizializzati
Utilizzando collections.defaultdict
, è possibile evitare errori grazie alla creazione automatica di chiavi mancanti.
from collections import defaultdict
data = defaultdict(lambda: defaultdict(dict))
data["level1"]["level2"]["key"] = "value"
# Accesso senza errori anche per chiavi mancanti
print(data["level1"]["level3"]["key"]) # Output: {}
Registrazione dei messaggi di errore
Catturare gli errori e registrarli facilita il debug e la risoluzione dei problemi.
import logging
logging.basicConfig(level=logging.ERROR)
try:
value = data["level1"]["level3"]["key"]
except KeyError as e:
logging.error(f"KeyError: {e}")
value = "default"
print(value) # Output: default
Importanza della validazione dei dati
Controllare la struttura dei dati prima di eseguire operazioni sui dizionari è anche una pratica utile.
def validate_data_structure(data):
if not isinstance(data, dict):
raise ValueError("Data must be a dictionary")
return True
try:
validate_data_structure(data)
except ValueError as e:
print(e)
Gestire correttamente gli errori contribuisce a migliorare la stabilità e l’affidabilità del codice. Nella prossima sezione, esploreremo le librerie utili per lavorare con i dizionari annidati.
Librerie per operare efficientemente con i dizionari
Esistono diverse librerie in Python che semplificano la gestione dei dizionari annidati. In questa sezione, esploreremo strumenti come pandas
, json
e altre librerie che semplificano le operazioni sui dizionari.
Operazioni con i dizionari usando pandas
La libreria pandas
è molto utile per convertire i dizionari in dataframe e manipolarli. È particolarmente utile quando si devono gestire dizionari annidati in un formato tabellare.
import pandas as pd
# Convertire un dizionario in un dataframe
data = {
"person1": {"name": "Alice", "age": 30},
"person2": {"name": "Bob", "age": 25},
}
df = pd.DataFrame.from_dict(data, orient="index")
print(df)
# Output:
# name age
# person1 Alice 30
# person2 Bob 25
Con questo metodo, puoi manipolare facilmente i dati del dizionario, filtrare e ordinare righe e colonne.
Operazioni con il modulo json
Il modulo json
è utilizzato per convertire i dizionari in formato JSON e viceversa. È utile per salvare i dizionari in file o per leggere dati esterni e trattarli come dizionari annidati.
import json
# Convertire un dizionario in una stringa JSON
data_json = json.dumps(data, indent=2)
print(data_json)
# Convertire una stringa JSON in un dizionario
loaded_data = json.loads(data_json)
print(loaded_data)
Il modulo è utile anche per la manipolazione di dati per semplificare la gestione di dizionari annidati.
Calcolo delle differenze tra dizionari con dictdiffer
Utilizzando la libreria dictdiffer
, è facile calcolare le differenze tra dizionari annidati. Questo è utile quando si devono rilevare modifiche nei dati.
from dictdiffer import diff
data1 = {"person1": {"name": "Alice", "age": 30}}
data2 = {"person1": {"name": "Alice", "age": 31}}
# Calcolare la differenza
difference = list(diff(data1, data2))
print(difference)
# Output: [('change', 'person1.age', (30, 31))]
Operazioni sui dizionari annidati con toolz
La libreria toolz
fornisce numerosi strumenti per lavorare con i dizionari annidati.
from toolz.dicttoolz import get_in, assoc_in
data = {"level1": {"level2": {"key": "value"}}}
# Ottenere valori in un dizionario annidato in modo sicuro
value = get_in(["level1", "level2", "key"], data)
print(value) # Output: value
# Impostare valori in un dizionario annidato
new_data = assoc_in(data, ["level1", "level2", "new_key"], "new_value")
print(new_data)
# Output: {'level1': {'level2': {'key': 'value', 'new_key': 'new_value'}}}
Semplificare i dizionari annidati con flatdict
La libreria flatdict
semplifica la gestione dei dizionari annidati, trasformandoli in strutture più semplici da manipolare.
import flatdict
data = {"level1": {"level2": {"key1": "value1", "key2": "value2"}}}
# Semplificare il dizionario
flat_data = flatdict.FlatDict(data)
print(flat_data)
# Output: FlatDict({'level1:level2:key1': 'value1', 'level1:level2:key2': 'value2'})
# Convertire i dati semplificati in un dizionario
nested_data = flat_data.as_dict()
print(nested_data)
Fondere dizionari con deepmerge
La libreria deepmerge
è utile per unire facilmente dizionari annidati complessi.
from deepmerge import always_merger
dict1 = {"level1": {"key1": "value1"}}
dict2 = {"level1": {"key2": "value2"}}
# Fusione di dizionari annidati
merged = always_merger.merge(dict1, dict2)
print(merged)
# Output: {'level1': {'key1': 'value1', 'key2': 'value2'}}
Utilizzando queste librerie, è possibile gestire i dizionari annidati in modo efficiente, semplificando anche le strutture dati più complesse. Nella prossima sezione, riassumeremo tutto ciò che è stato trattato.
Riepilogo
In questo articolo, abbiamo esplorato diversi metodi per lavorare con i dizionari annidati e multidimensionali in Python. Dai metodi di accesso di base alla gestione sicura, fino all’autoinizializzazione e alla gestione degli errori, abbiamo coperto un ampio spettro di tecniche. Inoltre, abbiamo visto come le librerie pandas
, json
e toolz
possano semplificare le operazioni sui dizionari.
Queste conoscenze aumentano notevolmente la flessibilità e l’efficienza quando si lavora con grandi set di dati gerarchici. I dizionari annidati sono una delle strutture dati più potenti in Python, e con la pratica, puoi sfruttarne al meglio il potenziale.