ChainMap - ricerca su molteplici dizionari

Scopo: La classe ChainMap gestisce una sequenza di dizionari, scorrendoli in ordine per cercare dei valori associati alle chiavi.

La classe ChainMap gestisce una sequenza di dizionari, scorrendoli in ordine per cercare dei valori associati alle chiavi. ChainMap è un buon contenitore di "contesto", visto che può essere trattato come uno stack con le modifiche che si manifestano mentre lo stack cresce, poi si scaricano mentre lo stack rimpicciolisce.

Accesso ai Valori

Per accedere ai valori, ChainMap supporta la stessa API di un normale dizionario.

# collections_chainmap_read.py

import collections

a = {'a': 'A', 'c': 'C'}
b = {'b': 'B', 'c': 'D'}

m = collections.ChainMap(a, b)

print('Valori individuali')
print('a = {}'.format(m['a']))
print('b = {}'.format(m['b']))
print('c = {}'.format(m['c']))
print()

print('Chiavi = {}'.format(list(m.keys())))
print('Valori = {}'.format(list(m.values())))
print()

print('Elementi:')
for k, v in m.items():
    print('{} = {}'.format(k, v))
print()

print('"d" in m: {}'.format(('d' in m)))

La ricerca avviene sui singoli dizionari nell'ordine con il quale sono stati passati al costruttore, quindi il valore riportato per la chiave 'c' proviene dal dizionario a.

$ python3 collections_chainmap_read.py

Valori individuali
a = A
b = B
c = C

Chiavi = ['c', 'b', 'a']
Valori = ['C', 'B', 'A']

Elementi:
c = C
b = B
a = A

"d" in m: False

Riordinare

ChainMap conserva la lista delle mappature sulle quali esegue le ricerche in una lista nel suo attributo maps. La lista è modificabile, quindi è possibile aggiungere direttamente nuove mappature oppure modificare l'ordine degli elementi per controllare la ricerca ed aggiornare il comportamento.

# collections_chainmap_reorder.py

import collections

a = {'a': 'A', 'c': 'C'}
b = {'b': 'B', 'c': 'D'}

m = collections.ChainMap(a, b)

print(m.maps)
print('c = {}\n'.format(m['c']))

# inverte la lista
m.maps = list(reversed(m.maps))

print(m.maps)
print('c = {}'.format(m['c']))

Quando l'ordine nella lista delle mappature viene invertito, il valore associato a 'c' cambia.

$ python3 collections_chainmap_reorder.py

[{'c': 'C', 'a': 'A'}, {'c': 'D', 'b': 'B'}]
c = C

[{'c': 'D', 'b': 'B'}, {'c': 'C', 'a': 'A'}]
c = D

Aggiornare Valori

Un ChainMap non effettua una cache dei valori di mappatura dei dizionari, quindi la modifica del loro contenuto si riflette sui risultati quando si accede a ChainMap.

# collections_chainmap_update_behind.py

import collections

a = {'a': 'A', 'c': 'C'}
b = {'b': 'B', 'c': 'D'}

m = collections.ChainMap(a, b)
print('Prima: {}'.format(m['c']))
a['c'] = 'E'
print('Dopo : {}'.format(m['c']))

La modifica dei valori associati a chiavi esistenti e l'aggiunta di nuovi elementi funziona allo stesso modo.

$ python3 collections_chainmap_update_behind.py

Prima: C
Dopo : E

E' anche possibile impostare direttamente i valori tramite ChainMap, sebbene solo la prima mappatura nella catena è realmente modificata.

# collections_chainmap_update_directly.py

import collections

a = {'a': 'A', 'c': 'C'}
b = {'b': 'B', 'c': 'D'}

m = collections.ChainMap(a, b)
print('Prima:', m)
m['c'] = 'E'
print('Dopo :', m)
print('a:', a)

Quando il nuovo valore viene conservato utilizzando m, la mappatura di a viene aggiornata.

$ python3 collections_chainmap_update_directly.py

Prima: ChainMap({'a': 'A', 'c': 'C'}, {'c': 'D', 'b': 'B'})
Dopo : ChainMap({'a': 'A', 'c': 'E'}, {'c': 'D', 'b': 'B'})
a: {'a': 'A', 'c': 'E'}

ChainMap fornisce un metodo di convenienza per la creazione di una nuova istanza con una mappatura supplementare all'inizio della lista in maps per evitare di dover modificare le struttura dati esistenti sottostanti.

# collections_chainmap_new_child.py

import collections

a = {'a': 'A', 'c': 'C'}
b = {'b': 'B', 'c': 'D'}

m1 = collections.ChainMap(a, b)
m2 = m1.new_child()

print('m1 prima:', m1)
print('m2 prima:', m2)

m2['c'] = 'E'

print('m1 dopo:', m1)
print('m2 dopo:', m2)

Il comportamento tipico delle strutture di stack rende conveniente l'uso di istanze di ChainMap come modelli o contesti di applicazione, visto che è facile aggiungere od aggiornare valori in una iterazione, poi scaricare le modifiche per l'iterazione successiva.

$ python3 collections_chainmap_new_child.py

m1 prima: ChainMap({'c': 'C', 'a': 'A'}, {'c': 'D', 'b': 'B'})
m2 prima: ChainMap({}, {'c': 'C', 'a': 'A'}, {'c': 'D', 'b': 'B'})
m1 dopo: ChainMap({'c': 'C', 'a': 'A'}, {'c': 'D', 'b': 'B'})
m2 dopo: ChainMap({'c': 'E'}, {'c': 'C', 'a': 'A'}, {'c': 'D', 'b': 'B'})

Per situazioni dove il nuovo contesto è conosciuto o costruito in anticipo, è anche possibile passare una mappatura a new_child().

# collections_chainmap_new_child_explicit.py

import collections

a = {'a': 'A', 'c': 'C'}
b = {'b': 'B', 'c': 'D'}
c = {'c': 'E'}

m1 = collections.ChainMap(a, b)
m2 = m1.new_child(c)

print('m1["c"] = {}'.format(m1['c']))
print('m2["c"] = {}'.format(m2['c']))

Questo è l'equivalente di.

m2 = collections.ChainMap(c, *m1.maps)

e produce.

$ python3 collections_chainmap_new_child_explicit.py
m1["c"] = C
m2["c"] = E

Vedere anche:

ChainMap
La documentazione della libreria standard per questo modulo
Note di Portabilità
Note di portabilità per collections.