Ho un dizionario nidificato. C'è solo un modo per ottenere valori in modo sicuro?
try:
example_dict['key1']['key2']
except KeyError:
pass
O forse Python ha un metodo simile get()
al dizionario nidificato?
except keyerror:
clausola.
Ho un dizionario nidificato. C'è solo un modo per ottenere valori in modo sicuro?
try:
example_dict['key1']['key2']
except KeyError:
pass
O forse Python ha un metodo simile get()
al dizionario nidificato?
except keyerror:
clausola.
Risposte:
Puoi usare get
due volte:
example_dict.get('key1', {}).get('key2')
Questo restituirà None
se uno key1
o key2
non esiste.
Si noti che ciò potrebbe comunque generare un AttributeError
if se example_dict['key1']
non è un dict (o un oggetto simile a un dict con un get
metodo). Il try..except
codice che hai pubblicato aumenterebbe TypeError
invece se non example_dict['key1']
è iscrivibile.
Un'altra differenza è che i try...except
cortocircuiti immediatamente dopo il primo tasto mancante. La catena di get
chiamate no.
Se desideri preservare la sintassi, example_dict['key1']['key2']
ma non vuoi che aumenti mai KeyErrors, puoi usare la ricetta di Hasher :
class Hasher(dict):
# https://stackoverflow.com/a/3405143/190597
def __missing__(self, key):
value = self[key] = type(self)()
return value
example_dict = Hasher()
print(example_dict['key1'])
# {}
print(example_dict['key1']['key2'])
# {}
print(type(example_dict['key1']['key2']))
# <class '__main__.Hasher'>
Si noti che questo restituisce un Hasher vuoto quando manca una chiave.
Poiché Hasher
è una sottoclasse di dict
te puoi usare un Hasher nello stesso modo in cui potresti usare undict
. Sono disponibili tutti gli stessi metodi e sintassi, gli hashter trattano le chiavi mancanti in modo diverso.
Puoi convertire un normale dict
in un Hasher
simile:
hasher = Hasher(example_dict)
e converti a Hasher
in normale dict
altrettanto facilmente:
regular_dict = dict(hasher)
Un'altra alternativa è nascondere la bruttezza in una funzione di aiuto:
def safeget(dct, *keys):
for key in keys:
try:
dct = dct[key]
except KeyError:
return None
return dct
Quindi il resto del codice può rimanere relativamente leggibile:
safeget(example_dict, 'key1', 'key2')
safeget
metodo non è molto sicuro in molti modi poiché sovrascrive il dizionario originale, il che significa che non puoi fare cose del genere safeget(dct, 'a', 'b') or safeget(dct, 'a')
.
dct = dct[key]
riassegna un nuovo valore alla variabile locale dct
. Questo non muta il dict originale (quindi il dict originale non è interessato da safeget
.) Se, d'altra parte, dct[key] = ...
fosse stato usato, allora il dict originale sarebbe stato modificato. In altre parole, in Python i nomi sono associati a valori . L'assegnazione di un nuovo valore a un nome non influisce sul vecchio valore (a meno che non ci siano più riferimenti al vecchio valore, nel qual caso (in CPython) verrà raccolta la spazzatura.)
safeget
metodo fallirà anche nel caso in cui esista la chiave di un dict nidificato, ma il valore è null. Lancerà TypeError: 'NoneType' object is not subscriptable
nella prossima iterazione
Puoi anche usare Python Red :
def deep_get(dictionary, *keys):
return reduce(lambda d, key: d.get(key) if d else None, keys, dictionary)
Combinando qui tutte queste risposte e le piccole modifiche che ho apportato, penso che questa funzione sarebbe utile. è sicuro, rapido, facilmente gestibile.
def deep_get(dictionary, keys, default=None):
return reduce(lambda d, key: d.get(key, default) if isinstance(d, dict) else default, keys.split("."), dictionary)
Esempio :
>>> from functools import reduce
>>> def deep_get(dictionary, keys, default=None):
... return reduce(lambda d, key: d.get(key, default) if isinstance(d, dict) else default, keys.split("."), dictionary)
...
>>> person = {'person':{'name':{'first':'John'}}}
>>> print (deep_get(person, "person.name.first"))
John
>>> print (deep_get(person, "person.name.lastname"))
None
>>> print (deep_get(person, "person.name.lastname", default="No lastname"))
No lastname
>>>
deep_get({'a': 1}, "a.b")
dà None
ma mi aspetterei un'eccezione simile KeyError
o qualcos'altro.
None
aRaise KeyError
Basandosi sulla risposta di Yoav, un approccio ancora più sicuro:
def deep_get(dictionary, *keys):
return reduce(lambda d, key: d.get(key, None) if isinstance(d, dict) else None, keys, dictionary)
Una soluzione ricorsiva. Non è il più efficiente, ma lo trovo un po 'più leggibile rispetto agli altri esempi e non si basa su funzioni.
def deep_get(d, keys):
if not keys or d is None:
return d
return deep_get(d.get(keys[0]), keys[1:])
Esempio
d = {'meta': {'status': 'OK', 'status_code': 200}}
deep_get(d, ['meta', 'status_code']) # => 200
deep_get(d, ['garbage', 'status_code']) # => None
Una versione più raffinata
def deep_get(d, keys, default=None):
"""
Example:
d = {'meta': {'status': 'OK', 'status_code': 200}}
deep_get(d, ['meta', 'status_code']) # => 200
deep_get(d, ['garbage', 'status_code']) # => None
deep_get(d, ['meta', 'garbage'], default='-') # => '-'
"""
assert type(keys) is list
if d is None:
return default
if not keys:
return d
return deep_get(d.get(keys[0]), keys[1:], default)
Mentre l'approccio di riduzione è pulito e breve, penso che un loop semplice sia più facile da eseguire. Ho anche incluso un parametro predefinito.
def deep_get(_dict, keys, default=None):
for key in keys:
if isinstance(_dict, dict):
_dict = _dict.get(key, default)
else:
return default
return _dict
Come esercizio per capire come funzionava il riduttore a una fodera, ho fatto quanto segue. Ma alla fine l'approccio ad anello mi sembra più intuitivo.
def deep_get(_dict, keys, default=None):
def _reducer(d, key):
if isinstance(d, dict):
return d.get(key, default)
return default
return reduce(_reducer, keys, _dict)
uso
nested = {'a': {'b': {'c': 42}}}
print deep_get(nested, ['a', 'b'])
print deep_get(nested, ['a', 'b', 'z', 'z'], default='missing')
Ti consiglio di provare python-benedict
.
È una dict
sottoclasse che fornisce supporto keypath e molto altro.
Installazione: pip install python-benedict
from benedict import benedict
example_dict = benedict(example_dict, keypath_separator='.')
ora puoi accedere ai valori nidificati usando keypath :
val = example_dict['key1.key2']
# using 'get' method to avoid a possible KeyError:
val = example_dict.get('key1.key2')
o accedi ai valori nidificati utilizzando l' elenco delle chiavi :
val = example_dict['key1', 'key2']
# using get to avoid a possible KeyError:
val = example_dict.get(['key1', 'key2'])
È ben testato e open-source su GitHub :
d.get('a.b[0].c[-1]')
Una classe semplice che può avvolgere un dict e recuperare in base a una chiave:
class FindKey(dict):
def get(self, path, default=None):
keys = path.split(".")
val = None
for key in keys:
if val:
if isinstance(val, list):
val = [v.get(key, default) if v else None for v in val]
else:
val = val.get(key, default)
else:
val = dict.get(self, key, default)
if not val:
break
return val
Per esempio:
person = {'person':{'name':{'first':'John'}}}
FindDict(person).get('person.name.first') # == 'John'
Se la chiave non esiste, viene restituita None
per impostazione predefinita. Puoi sovrascriverlo usando una default=
chiave nel FindDict
wrapper - ad esempio`:
FindDict(person, default='').get('person.name.last') # == doesn't exist, so ''
Dopo aver visto questo per ottenere gli attributi in profondità, ho fatto quanto segue per ottenere in sicurezza dict
valori nidificati usando la notazione a punti. Questo funziona per me perché i miei dicts
sono oggetti MongoDB deserializzati, quindi so che i nomi delle chiavi non contengono .
s. Inoltre, nel mio contesto, posso specificare un valore di fallback errato ( None
) che non ho nei miei dati, quindi posso evitare il modello try / tranne quando chiamo la funzione.
from functools import reduce # Python 3
def deepgetitem(obj, item, fallback=None):
"""Steps through an item chain to get the ultimate value.
If ultimate value or path to value does not exist, does not raise
an exception and instead returns `fallback`.
>>> d = {'snl_final': {'about': {'_icsd': {'icsd_id': 1}}}}
>>> deepgetitem(d, 'snl_final.about._icsd.icsd_id')
1
>>> deepgetitem(d, 'snl_final.about._sandbox.sbx_id')
>>>
"""
def getitem(obj, name):
try:
return obj[name]
except (KeyError, TypeError):
return fallback
return reduce(getitem, item.split('.'), obj)
fallback
non è effettivamente utilizzato nella funzione.
.
sep=','
parola chiave arg per generalizzare per determinate condizioni (sep, fallback). E @denvar, se obj
si dice di tipo int
dopo una sequenza di riduzione, allora obj [name] genera un TypeError, che catturo. Se invece avessi usato obj.get (nome) o obj.get (nome, fallback), avrebbe generato un AttributeError, quindi in entrambi i casi avrei dovuto catturarlo.
Ancora un'altra funzione per la stessa cosa, restituisce anche un valore booleano per indicare se la chiave è stata trovata o meno e gestisce alcuni errori imprevisti.
'''
json : json to extract value from if exists
path : details.detail.first_name
empty path represents root
returns a tuple (boolean, object)
boolean : True if path exists, otherwise False
object : the object if path exists otherwise None
'''
def get_json_value_at_path(json, path=None, default=None):
if not bool(path):
return True, json
if type(json) is not dict :
raise ValueError(f'json={json}, path={path} not supported, json must be a dict')
if type(path) is not str and type(path) is not list:
raise ValueError(f'path format {path} not supported, path can be a list of strings like [x,y,z] or a string like x.y.z')
if type(path) is str:
path = path.strip('.').split('.')
key = path[0]
if key in json.keys():
return get_json_value_at_path(json[key], path[1:], default)
else:
return False, default
esempio di utilizzo:
my_json = {'details' : {'first_name' : 'holla', 'last_name' : 'holla'}}
print(get_json_value_at_path(my_json, 'details.first_name', ''))
print(get_json_value_at_path(my_json, 'details.phone', ''))
(Vero, 'holla')
(Falso, '')
Puoi usare pydash:
import pydash as _
_.get(example_dict, 'key1.key2', default='Default')
Un adattamento della risposta di Unutbu che ho trovato utile nel mio codice:
example_dict.setdefaut('key1', {}).get('key2')
Genera una voce di dizionario per key1 se non ha già quella chiave in modo da evitare KeyError. Se vuoi finire un dizionario nidificato che include comunque l'associazione chiave come ho fatto io, questa sembra la soluzione più semplice.
Poco miglioramento reduce
nell'approccio per farlo funzionare con la lista. Utilizza anche il percorso dati come stringa divisa per punti anziché come matrice.
def deep_get(dictionary, path):
keys = path.split('.')
return reduce(lambda d, key: d[int(key)] if isinstance(d, list) else d.get(key) if d else None, keys, dictionary)
Una soluzione che ho usato che è simile al doppio get ma con la possibilità aggiuntiva di evitare un TypeError usando la logica if else:
value = example_dict['key1']['key2'] if example_dict.get('key1') and example_dict['key1'].get('key2') else default_value
Tuttavia, più nidifica il dizionario, più diventa ingombrante.
Per il dizionario nidificato / ricerche JSON, è possibile utilizzare dictor
pip installa dictor
oggetto dict
{
"characters": {
"Lonestar": {
"id": 55923,
"role": "renegade",
"items": [
"space winnebago",
"leather jacket"
]
},
"Barfolomew": {
"id": 55924,
"role": "mawg",
"items": [
"peanut butter jar",
"waggy tail"
]
},
"Dark Helmet": {
"id": 99999,
"role": "Good is dumb",
"items": [
"Shwartz",
"helmet"
]
},
"Skroob": {
"id": 12345,
"role": "Spaceballs CEO",
"items": [
"luggage"
]
}
}
}
per ottenere gli oggetti di Lonestar, è sufficiente fornire un percorso separato da punti, ad es
import json
from dictor import dictor
with open('test.json') as data:
data = json.load(data)
print dictor(data, 'characters.Lonestar.items')
>> [u'space winnebago', u'leather jacket']
puoi fornire un valore di fallback nel caso in cui la chiave non sia nel percorso
ci sono molte più opzioni che puoi fare, come ignorare il maiuscolo e usare altri caratteri oltre a "." come separatore di percorsi,
Ho cambiato poco questa risposta. Ho aggiunto verificando se stiamo usando la lista con i numeri. Quindi ora possiamo usarlo in qualunque modo. deep_get(allTemp, [0], {})
o deep_get(getMinimalTemp, [0, minimalTemperatureKey], 26)
ecc
def deep_get(_dict, keys, default=None):
def _reducer(d, key):
if isinstance(d, dict):
return d.get(key, default)
if isinstance(d, list):
return d[key] if len(d) > 0 else default
return default
return reduce(_reducer, keys, _dict)
Ci sono già molte buone risposte, ma ho trovato una funzione chiamata get like to lodash get in JavaScript land che supporta anche il raggiungimento degli elenchi per indice:
def get(value, keys, default_value = None):
'''
Useful for reaching into nested JSON like data
Inspired by JavaScript lodash get and Clojure get-in etc.
'''
if value is None or keys is None:
return None
path = keys.split('.') if isinstance(keys, str) else keys
result = value
def valid_index(key):
return re.match('^([1-9][0-9]*|[0-9])$', key) and int(key) >= 0
def is_dict_like(v):
return hasattr(v, '__getitem__') and hasattr(v, '__contains__')
for key in path:
if isinstance(result, list) and valid_index(key) and int(key) < len(result):
result = result[int(key)] if int(key) < len(result) else None
elif is_dict_like(result) and key in result:
result = result[key]
else:
result = default_value
break
return result
def test_get():
assert get(None, ['foo']) == None
assert get({'foo': 1}, None) == None
assert get(None, None) == None
assert get({'foo': 1}, []) == {'foo': 1}
assert get({'foo': 1}, ['foo']) == 1
assert get({'foo': 1}, ['bar']) == None
assert get({'foo': 1}, ['bar'], 'the default') == 'the default'
assert get({'foo': {'bar': 'hello'}}, ['foo', 'bar']) == 'hello'
assert get({'foo': {'bar': 'hello'}}, 'foo.bar') == 'hello'
assert get({'foo': [{'bar': 'hello'}]}, 'foo.0.bar') == 'hello'
assert get({'foo': [{'bar': 'hello'}]}, 'foo.1') == None
assert get({'foo': [{'bar': 'hello'}]}, 'foo.1.bar') == None
assert get(['foo', 'bar'], '1') == 'bar'
assert get(['foo', 'bar'], '2') == None