7

I have a python package of the form:

package
├── __init__.py
├── module_1.py
└── module_2.py

Inside __init__.py I have:

__all__ = ["module_1", "module_2"]

Module_1 is:

__all__ = ["foo"]
def foo(): pass

Module_2 is:

__all__ = ["Bar"]
class Bar: pass

From the documentation I thought that the following code would import foo and Bar:

import package as pk

However, when I run pk.foo() it throws an error: AttributeError: module 'package' has no attribute 'foo' (same for Bar).

Thanks to this answer, I know that to get the desired behavior, I can change __init__.py into:

from .module_1 import *
from .module_2 import *

The above works.

However, I do not understand the documentation. The lines:

if a package’s __init__.py code defines a list named __all__, it is taken to be the list of module names that should be imported when from package import * is encountered

Sound like my original __init__.py should have worked (the one with __all__ = ["module_1", "module_2"]). That is, the line import package as pk should have imported the modules module_1 and module_2, which in turn make foo and Bar available.

What am I missing?

EDIT:

I have also tried using exactly what the documentation mentions. That is, using from package import *, and then tried with package.foo() and foo(), but neither worked.

The first, package.foo(), throws the error NameError: 'package' is not defined.. The second, foo(), throws the error NameError: 'foo' is not defined..

How would a working example look like?.. One where __init__.py is of the form __all__ = ["module_1", "module_2"].

Andres Silva
  • 874
  • 1
  • 7
  • 26
  • 1
    First off, you should be looking at the Python 3 documentation. I've changed the URL in your question. – MattDMo Apr 17 '21 at 20:10
  • It says "when `from package import *` is encountered", not "when `import package` is encountered". – user2357112 Apr 17 '21 at 20:12
  • That is right.. I tried that too, but it didn't work either. I tried using `package.foo()` and just `foo()`, after `from package import *`. But neither worked. – Andres Silva Apr 17 '21 at 20:13
  • 1
    Plus, even if it triggered on `import package`, that still wouldn't have dumped the contents of `package.module_1` and `package.module_2` into the `package` namespace. – user2357112 Apr 17 '21 at 20:14
  • 1
    You're mixing up the behavior of `import whatever` and `from whatever import *` all over the place. – user2357112 Apr 17 '21 at 20:14
  • How would a working example look like? Where `__init__.py` is of the form `__all__ = ["module_1", "module_2"]`?.. Or is it only possible by using `from .module_1 import *` in `__init__`? – Andres Silva Apr 17 '21 at 20:19
  • Does this answer your question? [Can someone explain \_\_all\_\_ in Python?](https://stackoverflow.com/questions/44834/can-someone-explain-all-in-python) – Tomerikoo Apr 17 '21 at 20:39
  • @Tomerikoo, it does not. I actually reference that same post in my question. – Andres Silva Apr 17 '21 at 20:41
  • @juanpa.arrivillaga, that is true for regular modules, but the `__init__.py` file behaves different. See my next comment. – Andres Silva Apr 17 '21 at 20:42
  • @user2357112supportsMonica, I just did the following test: I added `print("Imported module_1")` inside `module_1`, and then ran `from package import *`. It printed the statement. So it is essentially working: It is importing the module. You are then right when saying "even if it triggered on import package, that still wouldn't have dumped the contents of package.module_1 and package.module_2 into the package namespace.". How would I achieve that behavior then? Or is it simply not meant to be used in this way? – Andres Silva Apr 17 '21 at 20:43
  • 1
    @AndresSilva you're right, I learned something new today. But in this case, what becomes available when you `from package import *` is `module_1` – juanpa.arrivillaga Apr 17 '21 at 20:57
  • @juanpa.arrivillaga, You are right! I can then use `module_1.foo()`. I guess I finally got how it works. But it would be nice to add all of this into a succinct Answer, to help future pythoners. – Andres Silva Apr 17 '21 at 21:02
  • in simple term, whatevery you define in `__all__` you can import those module/function/variable from that level only nothing else – sahasrara62 Apr 17 '21 at 22:52

2 Answers2

7

I will try to summarize the knowledge gained from the comments to my question, the documentation, my own tests, and this post.

1) __all__ behaves differently on __init__ and modules

1.1) Within a module

When __all__ is within a module, it determines what objects are made available when running from module import *.

Given this package structure:

package
├── __init__.py
├── module_1.py
└── module_2.py

And given the following code for module_1:

__all__ = ["foo"]
def foo(): pass
def baz(): pass

Running from package.module_1 import * will make foo available but not baz.

Moreover, foo can then be called using foo(), i.e., there is no need to reference the module.

1.2) Within __init__ (my original question)

When __all__ is within __init__,

it is taken to be the list of module names that should be imported when from package import * is encountered.

Running from package import * will then have two effects:

  1. The scripts of the modules in __all__ will be ran (they are imported).
  2. These modules are made available in the namespace.

This means that if __init__ is of the form:

__all__ = ["module_1", "module_2"]

Then, running from package import * will run the scripts of module_1 and module_2 and make both modules available. So, now, the function foo inside module_1 can be called as module_1.foo() instead of package.module_1.foo().

However, if this is the intent, then using from package.module_1 import foo might be better. As it makes foo accessible as foo().

2) from package import * is not the same as import package

Running from package import * has the effect mentioned in 1.2). However, this is not true for running import package: i.e. module_1.foo() would not work in this scenario.

3) The alternative approach to __init__

(The following is based on this post) As I mentioned in my question, there is an alternative approach to __init__, in which the objects that you want to make available when the user calls from package import * are directly imported into __init__.

As an example, __init__ could contain the following code:

from .module_1 import *
from .module_2 import *

Then, when the user calls from package import * the objects from modules 1 and 2 would be available on the namespace.

If module_1 was that of 1.1), then the function foo could be called without referencing the module. i.e. foo(). However, the same would not work for baz.

As mentioned in 2), from package import * is different to import package. Calling import package in this scenario (with this __init__), makes foo available through package.foo(), instead of just foo(). Similarly import package as pk makes foo available as pk.foo().

This approach could be preferable to that of 1.2), in which foo is made available through module_1.foo().

Andres Silva
  • 874
  • 1
  • 7
  • 26
  • Your 1.2 is wrong, it will *NOT* just affect modules in the package. The wording in the documentation is misleading (see my https://github.com/python/cpython/issues/106556) – calestyo Jul 09 '23 at 21:10
-3

The key problem in your code is that you have defined __all__ in sub modules. Which allows to be exported only that module, but such module is not available in your directory. They are definitions you want to use.

So, just remove them from sub modules and you'll get it working.

Bhojendra Rauniyar
  • 83,432
  • 35
  • 168
  • 231