How to access nested attributes or return a default value if any of the attributes doesn't exist?

Viewed 173

In JavaScript (and Node.js and TS), when we have an object A which has an attribute B, which has an attribute C, which has an attribute D and so on, we may use it like that:

const x = A?.B?.C?.D

Is there something similar in Python? I want x to be None if any of A, B, C, or D is None. Actually, I need x = A.B.C.D or DEFAULT_VALUE

What I do today is very verbose:

x = None
if A is not None and A.B is not None and A.B.C is not None:
    x = A.B.C.D

Is there a one-line solution in Python?

I prefer an approach like this, but it is still very verbose and I repeat myself. Looking for DRY.

x = A and A.B and A.B.C and A.B.C.D or DEFAULT_VALUE
3 Answers

There is probably no such concise solution in Python. The most "pythonic" way would probably be to "ask for forgiveness rather than permission" (see the "EAFP" entry in the Python Glossary), i.e. to use a try-except statement that directly tries to access D (no need for intermediate checks) and that fails gracefully by providing the required DEFAULT_VALUE in the error case:

try:
    x = A.B.C.D
except (NameError, AttributeError):
    x = DEFAULT_VALUE

You only need the NameError for the case that A might not exist at all. If you are sure that A exists (i.e. A has been defined and holds any value including None) you can drop it from the except clause.

I would define a function that allows you get the desired behavior in just one line:

def f(x, lst, default_val=None):
    for att in lst:
        if hasattr(x, attr):
            x = getattr(x, attr)
        else:
            return default_val
    return x

Now you can do things like this:

X = f(A, ['B', 'C', 'D'])

As suggested in the comments, if you don't need a default value, then you can just define the function as follows:

def f(x, lst):
    for att in lst:
        x = getattr(x, attr, None)
    return x

I think a one-liner would be very messy here tbh. If you have get available for whatever these objects are you could do something like:

b = A.get(B, None) if A else None
c = B.get(C, None) if B else None
x = C.get(D, None) if C else None

Generally Python favours more lines of code to make something explicit rather than a complex one-liner.

Related