Yuvalanmış bir sözlüğüm var. Değerleri güvenli bir şekilde çıkarmanın tek bir yolu var mı?
try:
example_dict['key1']['key2']
except KeyError:
pass
Ya da belki python get()
iç içe sözlük için bir yöntem var ?
except keyerror:
.
Yuvalanmış bir sözlüğüm var. Değerleri güvenli bir şekilde çıkarmanın tek bir yolu var mı?
try:
example_dict['key1']['key2']
except KeyError:
pass
Ya da belki python get()
iç içe sözlük için bir yöntem var ?
except keyerror:
.
Yanıtlar:
get
İki kez kullanabilirsiniz :
example_dict.get('key1', {}).get('key2')
None
Varsa key1
veya key2
yoksa bu geri döner .
Bunun hala bir AttributeError
if değerini yükseltebileceğini example_dict['key1']
, ancak bir diksiyon (veya bir get
yönteme sahip dikt benzeri bir nesne ) olmadığını unutmayın. try..except
Yayınladığınız kod getireceğini TypeError
eğer yerine example_dict['key1']
unsubscriptable olduğunu.
Diğer bir fark, try...except
ilk eksik anahtardan hemen sonra kısa devrelerin olmasıdır. Çağrı zinciri get
bunu yapmaz.
Sözdizimini korumak istiyorsanız, example_dict['key1']['key2']
ancak KeyError'ları yükseltmesini istemiyorsanız, Hasher tarifini kullanabilirsiniz :
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'>
Bir anahtar eksik olduğunda bunun boş bir Hasher döndürdüğünü unutmayın.
Çünkü Hasher
bir alt sınıfınız dict
bir Hasher'i aynı şekilde kullanabilirsiniz dict
. Tüm aynı yöntemler ve sözdizimi mevcuttur, Hashers sadece eksik anahtarlara farklı davranır.
Normal bir dönüştürebilirsiniz dict
bir içine Hasher
böyle:
hasher = Hasher(example_dict)
ve kolayca Hasher
bir normaline dönüştürün dict
:
regular_dict = dict(hasher)
Başka bir alternatif, çirkinliği bir yardımcı işlevde gizlemektir:
def safeget(dct, *keys):
for key in keys:
try:
dct = dct[key]
except KeyError:
return None
return dct
Böylece kodunuzun geri kalanı nispeten okunabilir kalabilir:
safeget(example_dict, 'key1', 'key2')
safeget
O güvenle gibi şeyler yapamaz, yani orijinal sözlüğü üzerine yazar beri yöntem çok güvenli olmayan yollarla bir çok olduğunu safeget(dct, 'a', 'b') or safeget(dct, 'a')
.
dct = dct[key]
yeniden atar yeni bir değer yerel değişken dct
. Bu orijinal dikdörtgeni değiştirmez (bu nedenle orijinal dikte bundan etkilenmez safeget
.) Öte yandan, dct[key] = ...
kullanılmışsa, orijinal dikti değiştirilmiş olurdu. Başka bir deyişle, Python'da isimler değerlere bağlıdır . Bir isme yeni bir değer
safeget
Ayrıca halinde iç içe geçmiş bir dict anahtar başarısız olur yöntem mevcuttur, ancak değer sıfırdır. Bir TypeError: 'NoneType' object is not subscriptable
sonraki yinelemede atılacak
Ayrıca python azaltma kullanabilirsiniz :
def deep_get(dictionary, *keys):
return reduce(lambda d, key: d.get(key) if d else None, keys, dictionary)
Tüm bu yanıtı ve yaptığım küçük değişiklikleri birleştirerek bu işlevin yararlı olacağını düşünüyorum. güvenli, hızlı, kolay bakım.
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)
Misal :
>>> 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")
verir None
ama böyle bir istisna KeyError
ya da başka bir şey beklenir .
None
içinRaise KeyError
Yoav'ın cevabına dayanarak, daha güvenli bir yaklaşım:
def deep_get(dictionary, *keys):
return reduce(lambda d, key: d.get(key, None) if isinstance(d, dict) else None, keys, dictionary)
Özyinelemeli bir çözüm. En verimli değil ama diğer örneklerden biraz daha okunabilir buluyorum ve functools'a dayanmıyor.
def deep_get(d, keys):
if not keys or d is None:
return d
return deep_get(d.get(keys[0]), keys[1:])
Misal
d = {'meta': {'status': 'OK', 'status_code': 200}}
deep_get(d, ['meta', 'status_code']) # => 200
deep_get(d, ['garbage', 'status_code']) # => None
Daha parlak bir versiyon
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)
Azaltma yaklaşımı düzgün ve kısa olsa da, basit bir döngünün grok yapmak daha kolay olduğunu düşünüyorum. Ayrıca varsayılan bir parametre ekledim.
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
Tek astarı azaltmanın nasıl çalıştığını anlamak için bir egzersiz olarak, aşağıdakileri yaptım. Ama sonuçta döngü yaklaşımı benim için daha sezgisel görünüyor.
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)
kullanım
nested = {'a': {'b': {'c': 42}}}
print deep_get(nested, ['a', 'b'])
print deep_get(nested, ['a', 'b', 'z', 'z'], default='missing')
Denemenizi öneririm python-benedict
.
dict
Anahtar yolu desteği ve çok daha fazlasını sağlayan bir alt sınıftır.
Kurulum: pip install python-benedict
from benedict import benedict
example_dict = benedict(example_dict, keypath_separator='.')
artık tuş yolunu kullanarak iç içe değerlere erişebilirsiniz :
val = example_dict['key1.key2']
# using 'get' method to avoid a possible KeyError:
val = example_dict.get('key1.key2')
veya anahtarlar listesini kullanarak iç içe değerlere erişin :
val = example_dict['key1', 'key2']
# using get to avoid a possible KeyError:
val = example_dict.get(['key1', 'key2'])
GitHub'da iyi test edilmiş ve açık kaynaklı :
d.get('a.b[0].c[-1]')
Bir dikdörtgeni saran ve bir anahtara dayalı olarak alabilen basit bir sınıf:
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
Örneğin:
person = {'person':{'name':{'first':'John'}}}
FindDict(person).get('person.name.first') # == 'John'
Anahtar yoksa, None
varsayılan olarak geri döner . Sarıcıdaki bir default=
anahtarı kullanarak bunu geçersiz kılabilirsiniz FindDict
- örneğin`:
FindDict(person, default='').get('person.name.last') # == doesn't exist, so ''
Özellikleri derinlemesine almak için bunu gördükten sonra , dict
nokta gösterimini kullanarak iç içe değerleri güvenle almak için aşağıdakileri yaptım . Bu benim için işe dicts
yaradı çünkü benim serileştirilmiş MongoDB nesneler, bu yüzden anahtar isimlerinin .
s içermediğini biliyorum . Ayrıca, benim bağlamda, ben None
benim verilerde olmayan bir falsy düşme değeri ( ) belirtebilirsiniz , bu yüzden işlevi çağırırken try / hariç desen önleyebilirsiniz.
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
aslında işlevde kullanılmaz.
.
sep=','
Belirli (sep, yedek) koşullar için genelleştirmek üzere bir anahtar kelime arg ekleyebilirsiniz . Ve @denvar, azaltma dizisinden sonra obj
tür söylenirse, int
obj [name] yakaladığım TypeError değerini yükseltir. Bunun yerine obj.get (name) veya obj.get (name, fallback) kullansaydım, AttributeError değerini yükseltirdi, bu yüzden her iki şekilde de yakalamam gerekirdi.
Yine aynı şey için başka bir işlev, anahtarın bulunup bulunmadığını göstermek için bir boole döndürür ve bazı beklenmedik hataları işler.
'''
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
örnek kullanım:
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', ''))
(Doğru, 'holla')
(Yanlış, '')
Pydash'ı kullanabilirsiniz:
import pydash as _
_.get(example_dict, 'key1.key2', default='Default')
Unutmayın ki kendi kodumda faydalı bulduğum cevap uyarlaması:
example_dict.setdefaut('key1', {}).get('key2')
KeyError'dan kaçınmanız için o tuşa sahip değilse, key1 için bir sözlük girdisi oluşturur. Benim yaptığım gibi o anahtar eşleşmesini içeren bir iç içe sözlük bitirmek istiyorsanız, bu en kolay çözüm gibi görünüyor.
reduce
Liste ile çalışmasını sağlamak için çok az gelişme . Ayrıca veri yolunu dizi yerine noktalara bölünerek dize olarak kullanma.
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)
Kullandığım bir çözüm double get'e benzer, ancak başka bir mantık kullanarak bir TypeError'dan kaçınma ek yeteneği ile:
value = example_dict['key1']['key2'] if example_dict.get('key1') and example_dict['key1'].get('key2') else default_value
Bununla birlikte, sözlük ne kadar iç içe olursa, bu o kadar hantal hale gelir.
İç içe sözlük / JSON aramaları için diktatör kullanabilirsiniz
pip install diktatör
dikte nesnesi
{
"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"
]
}
}
}
Lonestar'ın öğelerini almak için nokta ile ayrılmış bir yol sağlamanız yeterlidir;
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']
anahtarın yolda olmaması durumunda yedek değer sağlayabilirsiniz
Harf muhafazasını görmezden gelmek ve 'dışında' diğer karakterleri kullanmak gibi daha fazla seçenek yapabilirsiniz. yol ayırıcı olarak,
Bu cevabı çok az değiştirdim . Numaralı bir liste kullanıp kullanmadığımızı kontrol ettim. Şimdi onu her şekilde kullanabiliriz. deep_get(allTemp, [0], {})
ya deep_get(getMinimalTemp, [0, minimalTemperatureKey], 26)
vs
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)
Zaten çok sayıda iyi cevap var ama ben de indeks ile listelere ulaşmayı destekleyen JavaScript karada lodash get get adlı bir işlev ile geldim :
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