0

Yet another python imports question.

I have a directory structure

thing
  |- __init__.py
  |- run.py
  |- mod
      |- __init__.py
      |- what
           |- __init__.py
           |- yo.py

The contents of yo.py are

class Yo:
    def __init__(self):
        print("initialized What")

And the contents of my run.py are

from mod.what import yo

y = yo.Yo
print(y)
y()

Everything works great.

<class 'mod.what.yo.Yo'>
initialized What

But I need to import like this:

from mod import what

y = what.yo.Yo
print(y)
y()

and then I get

Traceback (most recent call last):
  File "/Users/pavelkomarov/Desktop/thing/run.py", line 4, in <module>
    y = what.yo.Yo
AttributeError: module 'mod.what' has no attribute 'yo'

Why? I've got __init__.pys out the wazoo. Shouldn't python be able to find the class? This structure is important to me because I have a lot of classes below a certain module and need to be able to access them all, preferably without having to do more granular imports of each one, which takes a ton of code.

Pavel Komarov
  • 1,153
  • 12
  • 26
  • `from mod import what` imports `__init__.py` but **not** `yo.py`. That's not how imports are working in Python. What's the reason that you _need to import like this_ ? – Maurice Meyer Dec 01 '21 at 23:52
  • So I'd need to chain import statements inside the `__init__.py`s in order for it to start working? I need to import like this because I'm not allowed to use * and I don't want 100,000 import statements followed by 100,000 uses when I could just do the 100,000 uses with slightly longer names. – Pavel Komarov Dec 01 '21 at 23:53
  • 1
    [`Explicit is better than implicit.`](https://www.python.org/dev/peps/pep-0020/). Might be helpful: https://stackoverflow.com/questions/32420646/how-to-expose-every-name-in-sub-module-in-init-py-of-a-package – Maurice Meyer Dec 01 '21 at 23:59
  • God that's so unbelievably gross. I've been using python for nearly a decade, and yet we're still getting confused about the basics and having to do these hacks! It's a major weakness of the language, and everyone knows it. – Pavel Komarov Dec 02 '21 at 00:02

1 Answers1

0

Thanks to Maurice and the people over at that other thread.

I've found putting

import importlib
import pkgutil

for mod_info in pkgutil.walk_packages(__path__, __name__ + '.'):
    mod = importlib.import_module(mod_info.name)

    # Emulate `from mod import *`
    try:
        names = mod.__dict__['__all__']
    except KeyError:
        names = [k for k in mod.__dict__ if not k.startswith('_')]

in mod's __init__.py does the trick. It also works if you put it in what's __init__.py.

I disagree that this is anything close to Zen, but it works.

Pavel Komarov
  • 1,153
  • 12
  • 26