Skip to content

Instantly share code, notes, and snippets.

@groz
Last active January 10, 2022 13:17
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save groz/f1838404d48971cc145609c226fdc6a2 to your computer and use it in GitHub Desktop.
Save groz/f1838404d48971cc145609c226fdc6a2 to your computer and use it in GitHub Desktop.
Python deep_get
# 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