0

I have an __init__ file in a folder foo/ which import some modules

from a import ClassA
from b import *

__all__ = [s for s in dir() if not s.startswith('_')]

My folder foo/ contains additional .py files

foo/
  a.py  # Contain ClassA
  b.py  # Contain ClassB
  c.py

a import c, so when I import a, it automatically import c and add it to the locals() scope of the __init__, even if c isn't imported in __init__.

I would like the __all__ of my init file to only contains the imports that I explicitly declared in the files (so just ClassA and ClassB). However, c, even if not imported in the __init__ is automatically added.

How can I dynamically compute __all__ to only contains the imports that I explicitly define in my __init__.py. Both locals() or dir() also return other files from the module foo.

Ideally, the solution should be both Py2.7 Py3 compatible.

Conchylicultor
  • 4,631
  • 2
  • 37
  • 40
  • 2
    Could not reproduce this - with this file structure and imports, `dir()` should not include `c`. Either way, `__all__` is not mandatory, and in this case omitting it will yield the result you want. – roeen30 Nov 10 '18 at 22:57
  • At least classes and functions carry a `__module__` attribute which identifies the module of their definition. You may need to filter which items really come from the desired modules. – Michael Butscher Nov 10 '18 at 23:02
  • @roeen30 I've run more test, so it seems that because `a` import `c`, `c` gets automatically added to `dir()` even if I haven't explicitly imported it. I updated the question. Also in this case I really need the `__all__` as it is used by an external program I don't control. – Conchylicultor Nov 10 '18 at 23:58
  • 1
    To check for directly vs. indirectly imported modules you may have to parse the file. – Michael Butscher Nov 11 '18 at 00:30

1 Answers1

1

Of course foo.c will show up in foos locals() once it is imported, that's how python imports work. __all__ is there to allow you to control what from foo import * does, by explicitly listing what should be imported.

So if you want from foo import * to import a and b, your __init__.py only needs to contain:

__all__ = ['a', 'b']

You don't need to import the submodules before that at all, only if you want to add code to your __init__.py that uses them.

So if you want to avoid the redundancy of having to import and add the submodules to __all__, just drop the imports and use __all__ explicitly.

mata
  • 67,110
  • 10
  • 163
  • 162
  • I know I can manually set `__all__`, but the question is about dynamically computing `__all__`. In practice, my imports are not just `import a` but `from a import MyClass`, `from b import *`, so just using `__all__` won't work. I updated the question – Conchylicultor Nov 11 '18 at 08:35
  • After importing stuff at package level there's not really any way to decide wheather a submodule was imported directly or indiretctly by some other submodule. Like @MichaelButscher said above, you'd have to parse the file to do so. Or try something like the decorator suggested [here](https://stackoverflow.com/questions/41895077/export-decorator-that-manages-all) to mark what you want exported. Which seems a lot of work to support star imports which are regarded as [bad parctice](https://stackoverflow.com/questions/2386714/why-is-import-bad) by many... – mata Nov 13 '18 at 21:42