0

I have the following code in module xyz:

Class Outer:
   Class Nested:
       pass

I can successfully instantiate Outer objects as follows

module = __import__("xyz", fromlist=[''])
the_class = getattr(module, "Outer")
instance = the_class()

However, when I replace "Outer" with "Outer.Nested" I get:

AttributeError: module 'xyz' has no attribute Outer.Nested

How can one make this work?

I should perhaps clarify that the above code is being used to instantiate classes whose type is unknown until runtime. Obviously I am not looking for instance = Outer.Nested().

OldSchool
  • 459
  • 1
  • 3
  • 14
  • 1
    So, just apply `getattr` twice, something like `outer, nested = "Outer.Nested".split('.'); klass = getattr(getattr(module, outer), nested)` – juanpa.arrivillaga Jan 21 '21 at 00:00
  • 1
    alternatively, use `operator.attrgetter` which will work with nested attribute access – juanpa.arrivillaga Jan 21 '21 at 00:01
  • @juanpa. Right on! Thanks so much. I just replaced the `getattr` line with `get_class = operator.attrgetter('Outer.Nested'); the_class = get_class(module)` and works great. I feel I should upvote and mark your answer as the correct answer. Will do so if you want to post an answer – OldSchool Jan 21 '21 at 00:21

3 Answers3

2

Two ways to do this, suppose you have a string representing attribute access and a nested object:

>>> from types import SimpleNamespace
>>> module = SimpleNamespace(foo=SimpleNamespace(bar=SimpleNamespace(baz='tada!')))
>>> module
namespace(foo=namespace(bar=namespace(baz='tada!')))

The first is to basically parse the string yourself by splitting and using getattr in a loop (or even, reduce!):

>>> from functools import reduce
>>> reduce(getattr, "foo.bar.baz".split('.'), module)
'tada!'

Which is just equivalent to:

>>> result = module
>>> for attr in "foo.bar.baz".split("."):
...     result = getattr(result, attr)
...
>>> result
'tada!'

Or use the built-in functools.attrgetter factory function as a one-off:

>>> import operator
>>> operator.attrgetter("foo.bar.baz")(module)
'tada!'
juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172
0

To build on @juanpa.arrivillaga's answer (but explain why):

Classes and Modules are treated as objects in Python. So when you declare a class in a module, the class is an attribute of the module; and when you declare an inner class, the inner class is an attribute of the outer class but not an attribute of the module. When you are handing in "Outer.Nested" to the getattr of the module, you are asking for the attribute of the module, whereby there is a . in the attribute's name. Not quite the same thing as the attribute of an attribute. Python's interpreter would be doing the "attribute of an attribute" when it is parsing, hence why you got confused.

carrvo
  • 511
  • 5
  • 11
0

By

I should perhaps clarify that the above code is being used to instantiate classes whose type is unknown until runtime. Obviously I am not looking for instance = Outer.Nested(). do you mean that the name is not known until runtime? All types in Python are determined at runtime anyway.

If so, you could alternatively do a fancy, but common trick where a caller can hand in the type at runtime.

# xyz.py
def get_instance(the_class):
  return the_class()
# unknown_third_party
Class Outer:
   Class Nested:
       pass
# main.py
from xyz import get_instance
import unknown_third_party
instance = get_instance(unknown_third_party.Outer.Nested)

You never need to know what is being handed in for your code to run--that is determined by your user/customer and at runtime.

Research Python's use of @ for syntactic sugar to find out why this is so common.

carrvo
  • 511
  • 5
  • 11