Search code examples
pythonpython-importpython-module

Import method from Python submodule in __init__, but not submodule itself


I have a Python module with the following structure:

mymod/
    __init__.py
    tools.py
# __init__.py
from .tools import foo
# tools.py
def foo():
    return 42

Now, when import mymod, I see that it has the following members:

mymod.foo()
mymod.tools.foo()

I don't want the latter though; it just pollutes the namespace.

Funnily enough, if tools.py is called foo.py you get what you want:

mymod.foo()

(Obviously, this only works if there is just one function per file.)

How do I avoid importing tools? Note that putting foo() into __init__.py is not an option. (In reality, there are many functions like foo which would absolutely clutter the file.)


Solution

  • The existence of the mymod.tools attribute is crucial to maintaining proper function of the import system. One of the normal invariants of Python imports is that if a module x.y is registered in sys.modules, then the x module has a y attribute referring to the x.y module. Otherwise, things like

    import x.y
    x.y.y_function()
    

    break, and depending on the Python version, even

    from x import y
    

    can break. Even if you don't think you're doing any of the things that would break, other tools and modules rely on these invariants, and trying to remove the attribute causes a slew of compatibility problems that are nowhere near worth it.


    Trying to make tools not show up in your mymod module's namespace is kind of like trying to not make "private" (leading-underscore) attributes show up in your objects' namespaces. It's not how Python is designed to work, and trying to force it to work that way causes more problems than it solves.

    The leading-underscore convention isn't just for instance variables. You could mark your tools module with a leading underscore, renaming it to _tools. This would prevent it from getting picked up by from mymod import * imports (unless you explicitly put it in an __all__ list), and it'd change how IDEs and linters treat attempts to access it directly.