1

I often see this pattern to navigate through dictionaries in a fault-tolerant manner:

a.get('k1', {}).get('k2', {}).get('k3', '')

This is, however, not possible if the intermediary objects are class instances rather than dictionaries. Is there a terse way to do (something like) the following:

a?.method1()?.method2()?.method3()...

Expanding to:

if a is None:
    return None
obj = a.method1()
if obj is None:
    return None
obj = obj.method2()
if obj is None:
    return None
obj = obj.method3()
return obj

I guess it might be possible with some variant of the builder pattern, but that would not be feasible if the methods above are in an API not controlled by the user.

juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172
Reinderien
  • 11,755
  • 5
  • 49
  • 77
  • You might be interested in my answer to another similar question [here](https://stackoverflow.com/questions/47879946/the-most-pythonic-way-to-access-elements-of-a-dict/47880053#47880053). Although it uses a custom dictionary implementation. – Skam Feb 13 '18 at 21:03
  • @Skam Maybe, but in this case the query cannot be a simple iterable of key strings; it would have to be an iterable of lambdas. – Reinderien Feb 13 '18 at 21:08
  • you need a "Maybe" monad – caoanan Sep 03 '20 at 08:14

2 Answers2

1

The closest equivalent would be to use getattr with a default value:

>>> class A: pass
...
>>> a = A()
>>> getattr(getattr(getattr(a, 'k1', A()), 'k2', A()), 'k3', A())
<__main__.A object at 0x104a7de10>

However, this above construction has major code-smell for me... why can you not rely on an object having an attribute?

Although, if you are chaining method calls then you would probably want a default callable that returns some convenient default value, e.g.:

>>> def empty(): return A()
...
>>> getattr(getattr(getattr(a, 'k1', empty)(), 'k2', empty)(), 'k3', empty)()
<__main__.A object at 0x104a7dda0>
>>>

If tersness is a concern, then maybe use reduce:

>>> def getter(obj, k):
...     return getattr(obj, k,  A())
...
>>> functools.reduce(getter, ('k1','k2','k3'), a)
<__main__.A object at 0x104a7dda0>

Honestly, I would use intermediate variables and explicitly call the methods and use exception handling, that would be the more Pythonic way.

juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172
1

Burn me alive for using eval but I occasionally like to use try...eval in cases where there are nested attributes.

try:
    val = eval("obj.{a1}.{a2}.{a3}".format(a1=a1, a2=a2, a3=a3))
except AttributeError:
    val = default
    print('Attribute not found')
except Exception as e:
    print('Something went wrong')

This looks nicer than multiple getattr's imo and I like to catch for AttributeErrors. Plus, it's pretty robust since you can do a lot of string manipulations to get just what you want. I also feel that this aligns with the "ask for forgiveness" schtick.

eltonlaw
  • 75
  • 1
  • 7