ฉันมีพจนานุกรมที่ซ้อนกัน มีวิธีเดียวเท่านั้นที่จะนำค่าออกมาใช้อย่างปลอดภัยหรือไม่?
try:
example_dict['key1']['key2']
except KeyError:
pass
หรือบางทีหลามอาจมีวิธีการที่เหมือนget()
กันสำหรับพจนานุกรมซ้อนกัน?
except keyerror:
ข้อได้เสมอ
ฉันมีพจนานุกรมที่ซ้อนกัน มีวิธีเดียวเท่านั้นที่จะนำค่าออกมาใช้อย่างปลอดภัยหรือไม่?
try:
example_dict['key1']['key2']
except KeyError:
pass
หรือบางทีหลามอาจมีวิธีการที่เหมือนget()
กันสำหรับพจนานุกรมซ้อนกัน?
except keyerror:
ข้อได้เสมอ
คำตอบ:
คุณสามารถใช้get
สองครั้ง:
example_dict.get('key1', {}).get('key2')
สิ่งนี้จะส่งคืนNone
หากมีkey1
หรือkey2
ไม่มีอยู่
โปรดทราบว่าสิ่งนี้ยังสามารถเพิ่มAttributeError
ถ้าexample_dict['key1']
มีอยู่ แต่ไม่ใช่ dict (หรือวัตถุที่คล้าย dict ด้วยget
เมธอด) try..except
รหัสคุณโพสต์จะเพิ่มTypeError
แทนถ้าexample_dict['key1']
เป็น unsubscriptable
ความแตกต่างอีกอย่างหนึ่งก็คือการtry...except
ลัดวงจรทันทีหลังจากคีย์แรกหายไป ห่วงโซ่ของget
การโทรไม่ได้
หากคุณต้องการรักษาไวยากรณ์example_dict['key1']['key2']
แต่ไม่ต้องการให้เพิ่ม KeyErrors คุณสามารถใช้สูตร 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'>
โปรดทราบว่าสิ่งนี้จะส่งคืน Hasher ที่ว่างเปล่าเมื่อไม่มีคีย์
ตั้งแต่Hasher
เป็น subclass ของdict
คุณสามารถใช้ Hasher dict
ในลักษณะเดียวกับที่คุณสามารถใช้ มีวิธีการและไวยากรณ์ที่เหมือนกันทั้งหมดที่มีอยู่ Hashers เพียงจัดการกับคีย์ที่หายไปต่างกัน
คุณสามารถแปลงค่าปกติdict
เป็นแบบHasher
นี้:
hasher = Hasher(example_dict)
และแปลงเป็นHasher
ค่าปกติdict
อย่างง่ายดาย:
regular_dict = dict(hasher)
อีกทางเลือกหนึ่งคือการซ่อนความอัปลักษณ์ในฟังก์ชันตัวช่วย:
def safeget(dct, *keys):
for key in keys:
try:
dct = dct[key]
except KeyError:
return None
return dct
ดังนั้นส่วนที่เหลือของรหัสของคุณสามารถอ่านได้ค่อนข้าง:
safeget(example_dict, 'key1', 'key2')
safeget
safeget(dct, 'a', 'b') or safeget(dct, 'a')
dct = dct[key]
reassignsค่าใหม่ให้กับตัวแปรท้องถิ่น dct
สิ่งนี้จะไม่กลายพันธุ์ dict ดั้งเดิม (ดังนั้น dict ดั้งเดิมจะไม่ได้รับผลกระทบจากsafeget
) ถ้าหากdct[key] = ...
มีการใช้งานในทางกลับกัน dict เดิมจะได้รับการแก้ไข ในคำอื่น ๆ ในหลามชื่อจะผูกพันกับค่า การกำหนดค่าใหม่ให้กับชื่อจะไม่ส่งผลกระทบต่อมูลค่าเดิม (เว้นแต่จะไม่มีการอ้างอิงไปยังค่าเก่าซึ่งในกรณีนี้ (ใน CPython) จะได้รับการรวบรวมขยะ)
safeget
วิธีการยังจะล้มเหลวในกรณีสำคัญของ Dict ที่ซ้อนกันอยู่ แต่ค่าที่เป็นโมฆะ มันจะTypeError: 'NoneType' object is not subscriptable
เข้าสู่รอบถัดไป
คุณสามารถใช้หลามลด :
def deep_get(dictionary, *keys):
return reduce(lambda d, key: d.get(key) if d else None, keys, dictionary)
ด้วยการรวมคำตอบทั้งหมดเหล่านี้ไว้ที่นี่และการเปลี่ยนแปลงเล็กน้อยที่ฉันทำฉันคิดว่าฟังก์ชั่นนี้จะมีประโยชน์ ปลอดภัยรวดเร็วบำรุงรักษาง่าย
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)
ตัวอย่าง:
>>> 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")
ให้None
แต่ฉันคาดหวังว่าจะมีข้อยกเว้นเช่นKeyError
หรืออย่างอื่น
None
เป็นRaise KeyError
สร้างคำตอบของ Yoav ซึ่งเป็นวิธีที่ปลอดภัยยิ่งขึ้น:
def deep_get(dictionary, *keys):
return reduce(lambda d, key: d.get(key, None) if isinstance(d, dict) else None, keys, dictionary)
โซลูชันแบบเรียกซ้ำ มันไม่ได้มีประสิทธิภาพมากที่สุด แต่ฉันคิดว่ามันสามารถอ่านได้มากกว่าตัวอย่างอื่น ๆ เล็กน้อยและไม่ได้พึ่งพาฟังก์ชั่น
def deep_get(d, keys):
if not keys or d is None:
return d
return deep_get(d.get(keys[0]), keys[1:])
ตัวอย่าง
d = {'meta': {'status': 'OK', 'status_code': 200}}
deep_get(d, ['meta', 'status_code']) # => 200
deep_get(d, ['garbage', 'status_code']) # => None
รุ่นขัดมันมากขึ้น
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)
ในขณะที่วิธีการลดนั้นสั้นและสั้น แต่ฉันคิดว่าการวนซ้ำง่ายนั้นง่ายกว่า ฉันได้รวมพารามิเตอร์เริ่มต้นแล้ว
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
ในการออกกำลังกายเพื่อทำความเข้าใจว่าการลดหนึ่งซับทำงานได้อย่างไรฉันทำสิ่งต่อไปนี้ แต่ในที่สุดวิธีการวนกลับดูเหมือนฉันจะเข้าใจง่ายขึ้น
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)
การใช้
nested = {'a': {'b': {'c': 42}}}
print deep_get(nested, ['a', 'b'])
print deep_get(nested, ['a', 'b', 'z', 'z'], default='missing')
python-benedict
ผมขอแนะนำให้คุณลอง
เป็นdict
คลาสย่อยที่ให้การสนับสนุน keypath และอีกมากมาย
การติดตั้ง: pip install python-benedict
from benedict import benedict
example_dict = benedict(example_dict, keypath_separator='.')
ตอนนี้คุณสามารถเข้าถึงค่าที่ซ้อนกันโดยใช้keypath :
val = example_dict['key1.key2']
# using 'get' method to avoid a possible KeyError:
val = example_dict.get('key1.key2')
หรือเข้าถึงค่าที่ซ้อนกันโดยใช้รายการคีย์ :
val = example_dict['key1', 'key2']
# using get to avoid a possible KeyError:
val = example_dict.get(['key1', 'key2'])
มันผ่านการทดสอบอย่างดีและโอเพนซอร์สบน GitHub :
d.get('a.b[0].c[-1]')
คลาสที่เรียบง่ายที่สามารถตัดคำสั่งและเรียกตามคีย์:
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
ตัวอย่างเช่น:
person = {'person':{'name':{'first':'John'}}}
FindDict(person).get('person.name.first') # == 'John'
หากไม่มีคีย์มันจะส่งกลับNone
โดยค่าเริ่มต้น คุณสามารถแทนที่ที่ใช้default=
คีย์ในFindDict
wrapper - ตัวอย่างเช่น:
FindDict(person, default='').get('person.name.last') # == doesn't exist, so ''
สำหรับการดึงกุญแจระดับที่สองคุณสามารถทำได้:
key2_value = (example_dict.get('key1') or {}).get('key2')
หลังจากได้เห็นสิ่งนี้เพื่อรับคุณสมบัติที่ลึกล้ำฉันทำสิ่งต่อไปนี้เพื่อรับdict
ค่าที่ซ้อนกันอย่างปลอดภัยโดยใช้เครื่องหมายจุด สิ่งนี้ใช้งานได้สำหรับฉันเพราะฉันdicts
เป็นวัตถุ MongoDB ที่ถูก deserialized ดังนั้นฉันรู้ว่าชื่อคีย์ไม่มี.
s นอกจากนี้ในบริบทของฉันฉันสามารถระบุค่าทางเลือกที่ผิดพลาด ( None
) ที่ฉันไม่มีในข้อมูลของฉันดังนั้นฉันสามารถหลีกเลี่ยงรูปแบบลอง / ยกเว้นเมื่อเรียกใช้ฟังก์ชัน
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
ไม่ได้ใช้จริงในฟังก์ชัน
.
sep=','
คำหลักเพื่อหาข้อสรุปสำหรับเงื่อนไขที่กำหนด (sep, fallback) และ @denvar ถ้าobj
พูดถึงชนิดint
หลังจากลำดับของการลดแล้ว obj [ชื่อ] เพิ่ม TypeError ซึ่งฉันจับ ถ้าฉันใช้ obj.get (ชื่อ) หรือ obj.get (ชื่อทางเลือก) แทนมันจะยกระดับ AttributeError ขึ้นมาดังนั้นฉันต้องจับ
อีกฟังก์ชั่นสำหรับสิ่งเดียวกันส่งคืนบูลีนเพื่อแสดงว่าพบคีย์หรือไม่และจัดการข้อผิดพลาดที่ไม่คาดคิด
'''
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
ตัวอย่างการใช้งาน:
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', ''))
(จริง 'holla')
(เท็จ, '')
คุณสามารถใช้ pydash:
import pydash as _
_.get(example_dict, 'key1.key2', default='Default')
การปรับคำตอบของ unutbu ที่ฉันพบว่ามีประโยชน์ในรหัสของฉัน:
example_dict.setdefaut('key1', {}).get('key2')
มันสร้างรายการพจนานุกรมสำหรับ key1 หากไม่มีคีย์นั้นอยู่แล้วเพื่อให้คุณหลีกเลี่ยง KeyError หากคุณต้องการท้ายพจนานุกรมที่ซ้อนกันซึ่งรวมถึงการจับคู่คีย์นั้นเหมือนที่ฉันทำนี่เป็นวิธีที่ง่ายที่สุด
เนื่องจากการเพิ่มข้อผิดพลาดที่สำคัญหากหนึ่งในคีย์ที่ขาดหายไปเป็นสิ่งที่สมเหตุสมผลที่ต้องทำเราจึงไม่สามารถตรวจสอบได้และทำให้มันเป็นหนึ่งเดียว:
def get_dict(d, kl):
cur = d[kl[0]]
return get_dict(cur, kl[1:]) if len(kl) > 1 else cur
การปรับปรุงเล็กน้อยเพื่อให้reduce
วิธีการทำงานกับรายการ นอกจากนี้ยังใช้เส้นทางข้อมูลเป็นสตริงหารด้วยจุดแทนอาร์เรย์
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)
วิธีการแก้ปัญหาที่ฉันใช้คล้ายกับ double get แต่มีความสามารถเพิ่มเติมเพื่อหลีกเลี่ยง TypeError โดยใช้ตรรกะอื่น:
value = example_dict['key1']['key2'] if example_dict.get('key1') and example_dict['key1'].get('key2') else default_value
อย่างไรก็ตามยิ่งมีพจนานุกรมซ้อนกันมากเท่าใด
สำหรับการค้นหาพจนานุกรม / JSON ที่ซ้อนกันคุณสามารถใช้ dictor
pip ติดตั้ง dictor
วัตถุ 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"
]
}
}
}
ในการรับไอเท็มของ Lonestar เพียงระบุเส้นทางที่คั่นด้วยจุดเช่น
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']
คุณสามารถระบุค่าทางเลือกในกรณีที่คีย์ไม่อยู่ในพา ธ
มีตัวเลือกมากมายที่คุณสามารถทำได้เช่นละเว้นการใส่ตัวอักษรและการใช้อักขระอื่นนอกเหนือจาก '.' เป็นตัวคั่นพา ธ
ฉันเปลี่ยนคำตอบนี้เล็กน้อย ฉันเพิ่มการตรวจสอบว่าเราใช้รายการกับตัวเลขหรือไม่ ดังนั้นตอนนี้เราสามารถใช้วิธีไหนก็ได้ deep_get(allTemp, [0], {})
หรือdeep_get(getMinimalTemp, [0, minimalTemperatureKey], 26)
อื่น ๆ
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)
มีคำตอบที่ดีอยู่แล้ว แต่ฉันได้ฟังก์ชั่นที่เรียกว่า getคล้ายกับ lodash get ในจาวาสคริปต์ที่สนับสนุนการเข้าถึงรายการตามดัชนี:
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