I prefer the `get_in()` method from Toolz: http://toolz.readthedocs.io/en/latest/api.html#toolz.dicttoo...
I prefer the `get_in()` method from Toolz: http://toolz.readthedocs.io/en/latest/api.html#toolz.dicttoo...
In addition to the string based lookup, it looks like there is an attempt at a pythonic approach:
from glom import T
spec = T['system']['planets'][-1].values()
glom(target, spec)
# ['jupiter', 69]
For me though, while I can understand what is going on, it doesn't feel pythonic.Here's what I would love to see:
from glom import nested
nested(target)['system']['planets'][-1].values()
And I would love (perhaps debatably) for that to be effectively equivalent to: nested(target).system.planets[-1].values()
Possible?--- edit: Ignore the above idea. I thought about this a bit more and the issue that your T object solves is that in my version:
nested(target)['system']
the result is ambiguous. Is this the end of the path query and should return original non-defaulting dict, or the middle of the path query and should return a defaulting dict? Unknown.The T object is a good solution for this.
def get_in(obj, lookup, default=None):
""" Walk obj via __getitem__ for each lookup,
returning the final value of the lookup or default.
"""
tmp = obj
for l in lookup:
try:
tmp = tmp[l]
except (KeyError, IndexError, TypeError):
return default
return tmp
data = {“foo”: {“bar”: [“spam”, “eggs”]}}
# find eggs
get_in(data, [“foo”, “bar”, 1])
By using __getitem__ you naturally work with anything in the python ecosystem.Here's a example comparison:
glom
glom(target, ('system.planets', ['name']))
# ['earth', 'jupiter']
toolz list(pluck('name', get_in(('system', 'planets'), target)))
# ['earth', 'jupiter']Check the post for more examples of where path-based access falls short of the mark!
(Sidenote: I find it very clunky that get_in() takes a "default" kwarg and _also_ a n"o_default" kwarg. Use a Sentinel object! http://boltons.readthedocs.io/en/latest/typeutils.html#bolto... )
DSL in strings = bad
DSL in native syntax = good
target = {'system': {'planets': [{'name': 'earth', 'moons': 1},
{'name': 'jupiter', 'moons': 69}]}}
glom(target, {'moon_count': ('system.planets', ['moons'], sum)})
# vs
def iter_moons(t):
for planet in target['system']['planets']:
yield planet['moons']
sum(iter_moons(target))
would have to combine with `defaultdict`s if your nested data is only sometimes there though>>> sum([x['moons'] for x in target['system']['planets']])
sum(planet['moons'] for planet in target['system']['planets']) sum(planet.get('moons', 0) for planet in target['system']['planets'])the very slight difference is that using T you must be explicit about attribute access vs key access whereas 'a.b.c' will try both
As a simple example, you get some syntax checking.
While mentioned in the post, it's not front-and-center because that's the sort of Python superpower that can look scary to less-experienced devs.
In my experience, it's almost impossible to dissuade people from generating strings if the API affords it. We can't even stop people from generating SQL by concatenation!
Musing: does Python have a way (Mypy?) to declare that a method can accept only constant expressions?