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?

Asked By: MystPi

||

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'>
Answered By: john-hen

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

Answered By: Hyperplane
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.