Last active
January 10, 2022 13:17
-
-
Save groz/f1838404d48971cc145609c226fdc6a2 to your computer and use it in GitHub Desktop.
Python deep_get
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# coding=utf-8 | |
from __future__ import unicode_literals | |
import collections | |
_default_stub = object() | |
def to_unicode(s): | |
if isinstance(s, str): | |
return unicode(s, 'utf8') | |
return unicode(s) | |
def deep_get(obj, path, default=_default_stub, separator='.'): | |
"""Get arbitrarily nested attribute or item value. | |
Args: | |
obj: Object to search in. | |
path (str, hashable, iterable of hashables): Arbitrarily nested path in obj hierarchy. | |
default: Default value. When provided it is returned if the path doesn't exist. | |
Otherwise the call raises a LookupError. | |
separator: String to split path by. | |
Returns: | |
Value at path. | |
Raises: | |
LookupError: If object at path doesn't exist. | |
Examples: | |
>>> deep_get({'a': 1}, 'a') | |
1 | |
>>> deep_get({'a': 1}, 'b') | |
Traceback (most recent call last): | |
... | |
LookupError: {u'a': 1} has no element at 'b' | |
>>> deep_get(['a', 'b', 'c'], -1) | |
u'c' | |
>>> deep_get({'a': [{'b': [1, 2, 3]}, 'some string']}, 'a.0.b') | |
[1, 2, 3] | |
>>> class A(object): | |
... def __init__(self): | |
... self.x = self | |
... self.y = {'a': 10} | |
... | |
>>> deep_get(A(), 'x.x.x.x.x.x.y.a') | |
10 | |
>>> deep_get({'a.b': {'c': 1}}, 'a.b.c') | |
Traceback (most recent call last): | |
... | |
LookupError: {u'a.b': {u'c': 1}} has no element at 'a' | |
>>> deep_get({'a.b': {'Привет': 1}}, ['a.b', 'Привет']) | |
1 | |
>>> deep_get({'a.b': {'Привет': 1}}, 'a.b/Привет', separator='/') | |
1 | |
""" | |
if isinstance(path, basestring): | |
attributes = path.split(separator) | |
elif isinstance(path, collections.Iterable): | |
attributes = path | |
else: | |
attributes = [path] | |
getattr_errors = (AttributeError, TypeError, UnicodeEncodeError) | |
getitem_errors = (TypeError, IndexError, KeyError) | |
conversion_errors = (UnicodeEncodeError, ValueError) | |
lookups = [ | |
(getattr, getattr_errors), | |
(lambda o, i: o[i], getitem_errors), | |
(lambda o, i: o[int(i)], getitem_errors + conversion_errors), | |
] | |
try: | |
for attr in attributes: | |
for lookup, errors in lookups: | |
try: | |
obj = lookup(obj, attr) | |
break | |
except errors: | |
pass | |
else: | |
msg = "{} has no element at '{}'".format(to_unicode(obj), to_unicode(attr)) | |
raise LookupError(msg.encode('utf8')) | |
except Exception: | |
if _default_stub != default: | |
return default | |
raise | |
return obj | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment