Tôi có một từ điển lồng nhau. Có cách nào để lấy giá trị ra một cách an toàn không?
try:
example_dict['key1']['key2']
except KeyError:
pass
Hoặc có thể python có một phương thức như get()
cho từ điển lồng nhau?
except keyerror:
mệnh đề.
Tôi có một từ điển lồng nhau. Có cách nào để lấy giá trị ra một cách an toàn không?
try:
example_dict['key1']['key2']
except KeyError:
pass
Hoặc có thể python có một phương thức như get()
cho từ điển lồng nhau?
except keyerror:
mệnh đề.
Câu trả lời:
Bạn có thể sử dụng get
hai lần:
example_dict.get('key1', {}).get('key2')
Điều này sẽ trở lại None
nếu một trong hai key1
hoặckey2
không tồn tại.
Lưu ý rằng điều này vẫn có thể tăng AttributeError
nếu example_dict['key1']
tồn tại nhưng không phải là một dict (hoặc một đối tượng giống như dict với một get
phương thức). Các try..except
mã bạn đăng tải sẽ nâng cao một TypeError
thay vì nếuexample_dict['key1']
là unsubscriptable.
Một điểm khác biệt là try...except
ngắn mạch ngay sau phím bị thiếu đầu tiên. Chuỗi get
cuộc gọi không.
Nếu bạn muốn giữ nguyên cú pháp, example_dict['key1']['key2']
nhưng không muốn nó tăng KeyErrors, thì bạn có thể sử dụng công thức 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'>
Lưu ý rằng điều này trả về một Hasher trống khi thiếu khóa.
Vì Hasher
là một lớp con của dict
bạn có thể sử dụng Hasher theo cách tương tự như bạn có thể sử dụngdict
. Tất cả các phương thức và cú pháp giống nhau đều có sẵn, Hashers chỉ xử lý các khóa bị thiếu khác nhau.
Bạn có thể chuyển đổi thường xuyên dict
thành Hasher
như thế này:
hasher = Hasher(example_dict)
và chuyển đổi Hasher
thành một thông thường dict
dễ dàng như sau:
regular_dict = dict(hasher)
Một cách khác là che giấu sự xấu xí trong chức năng của người trợ giúp:
def safeget(dct, *keys):
for key in keys:
try:
dct = dct[key]
except KeyError:
return None
return dct
Vì vậy, phần còn lại của mã của bạn có thể ở mức tương đối dễ đọc:
safeget(example_dict, 'key1', 'key2')
safeget
phương pháp là trong rất nhiều cách khác nhau không phải là rất an toàn vì nó ghi đè điển gốc, có nghĩa là bạn không thể làm một cách an toàn những thứ như safeget(dct, 'a', 'b') or safeget(dct, 'a')
.
safeget
không bao giờ ghi đè từ điển gốc. Nó sẽ trả về từ điển gốc, một giá trị từ từ điển gốc hoặc None
.
dct = dct[key]
gán lại một giá trị mới cho biến cục bộ dct
. Điều này không làm thay đổi chính tả ban đầu (do đó, chính tả ban đầu không bị ảnh hưởng bởi safeget
.) Nếu, mặt khác, dct[key] = ...
đã được sử dụng, thì chính tả ban đầu sẽ bị sửa đổi. Nói cách khác, trong tên Python bị ràng buộc với các giá trị . Việc gán giá trị mới cho tên không ảnh hưởng đến giá trị cũ (trừ khi không có thêm tham chiếu đến giá trị cũ, trong trường hợp đó (trong CPython), nó sẽ nhận được rác được thu thập.)
Bạn cũng có thể sử dụng python giảm :
def deep_get(dictionary, *keys):
return reduce(lambda d, key: d.get(key) if d else None, keys, dictionary)
Bằng cách kết hợp tất cả các câu trả lời ở đây và những thay đổi nhỏ mà tôi đã thực hiện, tôi nghĩ chức năng này sẽ hữu ích. nó an toàn, nhanh chóng, dễ bảo trì.
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)
Thí dụ :
>>> 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")
cho None
nhưng tôi sẽ mong đợi một ngoại lệ như KeyError
hoặc một cái gì đó khác.
None
thànhRaise KeyError
Dựa trên câu trả lời của Yoav, một cách tiếp cận thậm chí an toàn hơn:
def deep_get(dictionary, *keys):
return reduce(lambda d, key: d.get(key, None) if isinstance(d, dict) else None, keys, dictionary)
Một giải pháp đệ quy. Nó không phải là hiệu quả nhất nhưng tôi thấy nó dễ đọc hơn một chút so với các ví dụ khác và nó không dựa vào funcools.
def deep_get(d, keys):
if not keys or d is None:
return d
return deep_get(d.get(keys[0]), keys[1:])
Thí dụ
d = {'meta': {'status': 'OK', 'status_code': 200}}
deep_get(d, ['meta', 'status_code']) # => 200
deep_get(d, ['garbage', 'status_code']) # => None
Một phiên bản bóng bẩy hơn
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)
Trong khi phương pháp rút gọn là gọn gàng và ngắn gọn, tôi nghĩ rằng một vòng lặp đơn giản sẽ dễ dàng hơn để tìm kiếm. Tôi cũng đã bao gồm một tham số mặc định.
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
Như một bài tập để hiểu làm thế nào giảm một lớp lót hoạt động, tôi đã làm như sau. Nhưng cuối cùng, cách tiếp cận vòng lặp có vẻ trực quan hơn đối với tôi.
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)
Sử dụng
nested = {'a': {'b': {'c': 42}}}
print deep_get(nested, ['a', 'b'])
print deep_get(nested, ['a', 'b', 'z', 'z'], default='missing')
Tôi đề nghị bạn thử python-benedict
.
Nó là một dict
lớp con cung cấp hỗ trợ keypath và nhiều hơn nữa.
Cài đặt: pip install python-benedict
from benedict import benedict
example_dict = benedict(example_dict, keypath_separator='.')
bây giờ bạn có thể truy cập các giá trị lồng nhau bằng cách sử dụng keypath :
val = example_dict['key1.key2']
# using 'get' method to avoid a possible KeyError:
val = example_dict.get('key1.key2')
hoặc truy cập các giá trị lồng nhau bằng cách sử dụng danh sách khóa :
val = example_dict['key1', 'key2']
# using get to avoid a possible KeyError:
val = example_dict.get(['key1', 'key2'])
Nó được kiểm tra tốt và mã nguồn mở trên GitHub :
d.get('a.b[0].c[-1]')
Một lớp đơn giản có thể bao bọc một lệnh và truy xuất dựa trên khóa:
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
Ví dụ:
person = {'person':{'name':{'first':'John'}}}
FindDict(person).get('person.name.first') # == 'John'
Nếu khóa không tồn tại, nó sẽ trả về None
theo mặc định. Bạn có thể ghi đè bằng cách sử dụng một default=
khóa trong FindDict
trình bao bọc - ví dụ`:
FindDict(person, default='').get('person.name.last') # == doesn't exist, so ''
Sau khi thấy điều này để nhận được các thuộc tính sâu sắc, tôi đã thực hiện như sau để có được các dict
giá trị lồng nhau một cách an toàn bằng cách sử dụng ký hiệu dấu chấm. Điều này làm việc cho tôi vì dicts
các đối tượng MongoDB đã được khử lưu huỳnh của tôi , vì vậy tôi biết các tên khóa không chứa .
s. Ngoài ra, trong ngữ cảnh của tôi, tôi có thể chỉ định giá trị dự phòng giả ( None
) mà tôi không có trong dữ liệu của mình, vì vậy tôi có thể tránh mẫu thử / ngoại trừ khi gọi hàm.
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
không thực sự được sử dụng trong hàm.
.
sep=','
từ khóa arg để khái quát hóa cho các điều kiện (sep, dự phòng) nhất định. Và @denvar, nếuobj
nói về loại int
sau một chuỗi giảm, thì obj [name] sẽ tăng TypeError, mà tôi bắt được. Thay vào đó, nếu tôi sử dụng obj.get (tên) hoặc obj.get (tên, dự phòng), nó sẽ tăng AttributionError, do đó, dù sao tôi cũng cần phải nắm bắt.
Tuy nhiên, một chức năng khác cho điều tương tự, cũng trả về một boolean để thể hiện xem khóa có được tìm thấy hay không và xử lý một số lỗi không mong muốn.
'''
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
sử dụng ví dụ:
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', ''))
(Đúng, 'holla')
(Sai, '')
Bạn có thể sử dụng pydash:
import pydash as _
_.get(example_dict, 'key1.key2', default='Default')
Một bản phóng tác của câu trả lời của unutbu mà tôi thấy hữu ích trong mã của riêng mình:
example_dict.setdefaut('key1', {}).get('key2')
Nó tạo ra một mục từ điển cho key1 nếu nó chưa có khóa đó để bạn tránh KeyError. Nếu bạn muốn kết thúc một từ điển lồng nhau bao gồm cả việc ghép khóa đó như tôi đã làm, thì đây có vẻ là giải pháp đơn giản nhất.
Cải thiện ít để reduce
tiếp cận để làm cho nó hoạt động với danh sách. Cũng sử dụng đường dẫn dữ liệu dưới dạng chuỗi chia cho các dấu chấm thay vì mảng.
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)
Một giải pháp tôi đã sử dụng tương tự như nhận kép nhưng với khả năng bổ sung để tránh TypeError sử dụng nếu logic khác:
value = example_dict['key1']['key2'] if example_dict.get('key1') and example_dict['key1'].get('key2') else default_value
Tuy nhiên, từ điển càng lồng nhau thì điều này càng trở nên cồng kềnh.
Đối với tra cứu từ điển / JSON lồng nhau, bạn có thể sử dụng dictor
Pip cài đặt độc tài
đối tượng chính tả
{
"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"
]
}
}
}
để có được các vật phẩm của Lonestar, chỉ cần cung cấp một đường dẫn được phân tách bằng dấu chấm, nghĩa là
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']
bạn có thể cung cấp giá trị dự phòng trong trường hợp khóa không có trong đường dẫn
Có rất nhiều tùy chọn bạn có thể làm, như bỏ qua vỏ chữ cái và sử dụng các ký tự khác ngoài '.' như một đường phân cách
Tôi ít thay đổi câu trả lời này . Tôi đã thêm kiểm tra nếu chúng tôi sử dụng danh sách với các số. Vì vậy, bây giờ chúng ta có thể sử dụng nó bất cứ cách nào. deep_get(allTemp, [0], {})
hoặc deep_get(getMinimalTemp, [0, minimalTemperatureKey], 26)
v.v.
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)
Đã có rất nhiều câu trả lời hay nhưng tôi đã đưa ra một hàm gọi là get tương tự như lodash get trong vùng đất JavaScript cũng hỗ trợ tiếp cận danh sách theo chỉ mục:
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