How to hide internal modules in a package's namespace?
Question:
My Python package, tinted, finally works. However, when I run dir(tinted)
, the core.py
and sequences.py
files exist in the package! I want only the function tint
to be included in the package.
Current output of dir(tinted)
:
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__version__', 'core', 'sequences', 'tint']
Desired output of dir(tinted)
:
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__version__', 'tint']
This is my first time ever uploading a package to PyPi, so please forgive me if I’ve done anything wrong.
Anyone know how to fix this?
Answers:
Let’s say you have your Python package in a folder named tinted
. You have a function named tint
defined in module core.py
inside that folder. But you want to expose that function at the top level of the package’s namespace. Which is good practice for important functions, they should be at the top level.
Then in __init__.py
you would have this:
from .core import tint
(Or from tinted.core import tint
if you happen to use absolute imports.)
However, as you have noted, this also puts the core
module into the package’s top-level namespace:
>>> import tinted
>>> dir(tinted)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__',
'__name__', '__package__', '__path__', '__spec__', 'core', 'tint']
If you want to keep that namespace squeaky-clean, you can remove the internal module after the import
in __init__.py
. Like so:
from .core import tint
del core
Then:
>>> dir(tinted)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__',
'__name__', '__package__', '__path__', '__spec__', 'tint']
This is however not commonly done. Even packages in Python’s standard library don’t usually remove these kind of imports. For example, the logging
package imports threading
and just leaves it there. Though sometimes they do clean up a bit. Like the copy
module, which does del types, weakref
at the end. So there’s nothing wrong with it either.
In this case, the object was a function. Just note that, when it’s a class, you cannot completely hide the package structure anyway. Consider this example from the popular Pandas library:
>>> import pandas
>>> pandas.DataFrame
<class 'pandas.core.frame.DataFrame'>
Not sure if this breaks anything, but one can do
"""Module Docstring"""
__all__ = [
# Classes
"Foo",
# Functions
"bar",
]
__ALL__ = dir() + __all__ # catch default module attributes.
# Imports go here
def __dir__() -> list[str]:
return __ALL__
Explanation: dir(obj)
tries to call obj.__dir__()
. Modules are objects as well, and we can add a custom __dir__
method. Using this setup, you should get
dir(module) = ['__all__', '__builtins__', '__cached__',
'__doc__', '__file__', '__name__',
'__package__', '__spec__', 'Foo', 'bar']
Reference: PEP 562
My Python package, tinted, finally works. However, when I run dir(tinted)
, the core.py
and sequences.py
files exist in the package! I want only the function tint
to be included in the package.
Current output of dir(tinted)
:
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__version__', 'core', 'sequences', 'tint']
Desired output of dir(tinted)
:
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__version__', 'tint']
This is my first time ever uploading a package to PyPi, so please forgive me if I’ve done anything wrong.
Anyone know how to fix this?
Let’s say you have your Python package in a folder named tinted
. You have a function named tint
defined in module core.py
inside that folder. But you want to expose that function at the top level of the package’s namespace. Which is good practice for important functions, they should be at the top level.
Then in __init__.py
you would have this:
from .core import tint
(Or from tinted.core import tint
if you happen to use absolute imports.)
However, as you have noted, this also puts the core
module into the package’s top-level namespace:
>>> import tinted
>>> dir(tinted)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__',
'__name__', '__package__', '__path__', '__spec__', 'core', 'tint']
If you want to keep that namespace squeaky-clean, you can remove the internal module after the import
in __init__.py
. Like so:
from .core import tint
del core
Then:
>>> dir(tinted)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__',
'__name__', '__package__', '__path__', '__spec__', 'tint']
This is however not commonly done. Even packages in Python’s standard library don’t usually remove these kind of imports. For example, the logging
package imports threading
and just leaves it there. Though sometimes they do clean up a bit. Like the copy
module, which does del types, weakref
at the end. So there’s nothing wrong with it either.
In this case, the object was a function. Just note that, when it’s a class, you cannot completely hide the package structure anyway. Consider this example from the popular Pandas library:
>>> import pandas
>>> pandas.DataFrame
<class 'pandas.core.frame.DataFrame'>
Not sure if this breaks anything, but one can do
"""Module Docstring"""
__all__ = [
# Classes
"Foo",
# Functions
"bar",
]
__ALL__ = dir() + __all__ # catch default module attributes.
# Imports go here
def __dir__() -> list[str]:
return __ALL__
Explanation: dir(obj)
tries to call obj.__dir__()
. Modules are objects as well, and we can add a custom __dir__
method. Using this setup, you should get
dir(module) = ['__all__', '__builtins__', '__cached__',
'__doc__', '__file__', '__name__',
'__package__', '__spec__', 'Foo', 'bar']
Reference: PEP 562