Enhancing `getattr` to support nested attribute access with dotted strings

I propose extending Python’s built-in getattr function to support nested attribute access using dotted strings. This feature would simplify retrieving deeply nested attributes dynamically without the need for custom implementations or looping. For example:

Current workaround

value = getattr(getattr(getattr(getattr(myobj, 'employee'), 'department'), 'manager'), 'name')
# or with a custom implementation
def deep_getattr(obj, attr):
    for key in attr.split('.'):
        obj = getattr(obj, key)
    return obj
value = deep_getattr(myobj, 'employee.department.manager.name')

Proposed built-in support

value = getattr(myobj, 'employee.department.manager.name')

Benefits:

  • Reduces boilerplate code for nested attribute traversal.
  • Aligns with Python’s philosophy of simplicity and readability.
  • Useful for dynamic object traversal in frameworks, APIs, and data-driven applications.

Questions to Discuss:

  1. Should this be a built-in feature, or remain a helper function?
  2. Should the feature also handle indexed access for lists (e.g., user.children[0].first_name)? (imo, no need)

Discussion Question:
Would this feature simplify enough common use cases to justify adding it to the standard library, or is it too niche for inclusion?

1 Like

This does already exist in a slightly different form - operator.attrgetter('employee.department.manager.name')(obj), though probably you’d store the getter object. Putting this in getattr() would be a bad idea though - getattr/setattr are explicitly documented as allowing any string for attributes, including dotted names. Objects can store under any name, it’s just that .attr syntax limits what names are possible there. So adding this would be backwards incompatible.

15 Likes

You usually write myobj.employee.department.manager.name instead of getattr(getattr(getattr(getattr(myobj, 'employee'), 'department'), 'manager'), 'name').

6 Likes

To give the original poster the benefit of the doubt, I can only presume that the example they showed was for demonstration. The use case for getattr is generally to have a dynamic attribute name, or to be used with the default to avoid ugly try statements.

2 Likes

Not that this is good practice, but this precise change isn’t workable, for the following reason of perfectly valid code:

class A: pass
z = A()
setattr(z, "abc.xyz", 1)
print(getattr(z, "abc.xyz")) # prints 1

I know a few libraries that allow for arbitrary fields with this for, e.g. setting properties on graph nodes or other things.

2 Likes

Thank you for pointing that out! I hadn’t considered it fully in the context of this proposal.

1 Like

Thank you for bringing this up! If modifying getattr were still on the table, an optional keyword argument (e.g., nested=True) could distinguish between regular and dotted-name access, preserving compatibility. For example:

value = getattr(obj, "abc.xyz")  # Existing behavior
value = getattr(obj, "abc.xyz", nested=True)  # New one

This was just the idea I was going to give if some would say this.
But I now guess this proposal is not the right fit for Python’s design principles or practical implementation.

1 Like

True, but this targets dynamic access when the path is a runtime string like "employee.department.manager.name". Tools like operator.attrgetter help, and I appreciate the feedback!

1 Like

In case of dynamic access you would need first to create a dot-separated string from dynamic names, and then getattr would need to split it on separate attribute names. This is not particularly efficient.

2 Likes

Another way to implement this yourself:

import functools
def deep_getattr(obj, attr):
    return functools.reduce(getattr, attr.split('.'), obj)
7 Likes

Got the point! Thanks for the feedback, will appreciate that!
I’ll try to research more on my next feature req and look for more advanced change suggestions. :+1:

I ran into a case where this would be helpful again today, but for a reason I don’t think anybody brought up yet — the default argument.

Needing to access a specific nested attribute in structured data, where somewhere down the line, some of the attributes might start to be missing, because they’re only available under certain scenarios, the best implementation is using a try ... except block. I find this a bit overly verbose and unnecessary.

try:
    some_flag = a.b.c.d.e.f.g
except AttributeError:
    some_flag = False

In my case, I was already inside a try ... except block, which is what finally prompted me to propose a change / join a proposal discussion.

From a technical point of view, I think implementing nested access in getattr is a bit problematic, because, like it or not, even though they cannot be accessed via normal syntax, attributes can have dots in their name. On top of that, operator.attrgetter already exists, which implements nested access already, and isn’t such a critical component of the language.

I don’t really see any reason why operator.attrgetter, and for consistency operator.itemgetter, couldn’t get a default keyword argument, bringing it on-par with getattr’s functionality.
The main technical consideration here, I think, are which exceptions should they suppress. For operator.attrgetter, I think is pretty straight-forward — only AttributeError. For operator.itemgetter, I think it’s a fair a bit more common to possibly to encounter exceptions other than KeyError, but I think only suppressing KeyError is a very reasonable implementation choice.

2 Likes

I would find it useful to be able to access nested attributes either with getattr with a keyword argument nested=True, as proposed. I am accessing nested attributes as specified in a config file, therefore, the dynamic access is needed and having it nested would simplify the code a lot.

With that also comes a need for hasattr, to be able to easily check the existence of such a nested attribute. I am able to use try/except statement with the nested version of getattr (functools.reduce or operator.attrgetter one), but since the AttributeError might occur quite often, checking for the existence without needing to go through exception handling would be preferable performance-wise.

There’s really no reason to have getattr with a keyword argument; the exact same effect can be had with a brand new function.

Have you tested this? First find out whether hasattr is actually materially slower than checking for the exception. Keep in mind that it may very well be using exception handling internally, which would remove a lot of the difference; and what difference remains is likely to be on the order of nanoseconds per lookup. Find out whether it makes enough difference to make a difference.

I have no preference in how this is exposed in Python, I just wanted to express my interest in having such functionality implemented natively.

You are right, sorry for my confusion. Citing from docs of hasattr:

This is implemented by calling getattr(object, name) and seeing whether it raises an AttributeError or not.

I expect no more-performant yet consistent implementation can be made. But still, if it was worth it to have hasattr implemented as a built-in function, a nested variant would make sense just out of pure consistency (if such getattr is implemented). Even if it was a simple try/except block with returns of booleans.

That’s fair, but it’s worth keeping in mind that you don’t have to wait for upstream if it’s a new function. There’s a lengthy process to get this sort of change approved, and then it has to be implemented (that might seem easy if the implementation has already been provided, but someone needs to write tests and docs too) and merged, and then either you need to build Python from source or wait for the next release. Meanwhile, you can simply use the implementation directly by copying it into your own project.

That’s not necessarily true, which is why I suggested meaasuring; sometimes, a C-implemented function can short-cut parts of the exception instantiation. But yes, I do agree that, if a deep getattr is created, a deep hasattr would make sense - most likely as a try/except around the deep getattr, to ensure that their behaviour matches.