I noticed unexpected behavior when importing a submodule with an alias in my package's __init__.py
file. In short, using an alias in __init__.py
still exposes the original submodule name in the package namespace.
Specifically, this is how to reproduce the result:
.
├── package
│ ├── __init__.py
│ └── submodule.py
└── test.py
__init__.py
from . import submodule as sm
print(dir())
test.py
import package
print(package.submodule)
test.py
would get['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'sm', 'submodule']
<module 'package.submodule' from path_to_submodule.py>
You can see the submodule is exposed even when I import it as an alias in __init__.py
.
I expect that the test.py
should not have access to package.submodule
because the __init__.py
didn't import it directly. And I am not sure if this is a bug in python.
The point in this question is that the from X import Y as Z
doesn't create two variables Y
and Z
in the normal situation. For example, if I use from package import submodule as sm
in test.py
, and then print(dir())
, the result shows that it doesn't have access to submodule
. However, in this question, it actually creates two variables submodule
and sm
.
I try to ask ChatGPT or other LLMs and search the doc from python.org. However, I didn't get a clear answer. Is this a bug or a normal design?
You need to differentiate between two different cases.
__init__.py
; then package
can also not access it until it is explicitly imported.import package
package.submodule # <-- Error
# OK if imported
import package.submodule
package.submodule
submodule
, alias or not, submodule
will be put into the locals of package.__init__
You might be surprised, but this code works, explanation added at the end:
import .submodule as sm
del submodule # Looks like a NameError but isnt
However, you will get an attribute error in the following code, that nobody would expect:
import package.submodule
package.submodule # <-- AttributeError
So theoretically you can just delete the submodule
variable, however a) do not rely on it that it will work with all python versions and in the future, and more importantly b) you will create unintuitive code.
The recommended way is to rename it to _submodule
with the _
underscore telling the user that it is private and not intended to be used - some linters might also not show it for auto completion.
Why does aliasing create two variables?.
First is that the usage of import
and its variants from, as
is sugar syntax make use of the __import__
function with some syntactic sugar:
Roughly from . import submodule as sm
executes to:
# in .
import submodule
sm = submodule
To differentiate, this happens in the respective package's __init__
and not the file you are executing.
For example, from package import submodule as sm
, will neither add package
nor submodule
to your current locals.
Except, this statement is written in package.__init__
itself.
Similarly if you have modules a,b,c
and do from a.b import c
then b
will be added to a
and c
to b
.