4

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.)

Nico Schlömer
  • 53,797
  • 27
  • 201
  • 249
  • 1
    If I understand what you are doing, this is somewhat similar in content to the question I had before: [Any way for python file name to not end up in fully qualified name?](https://stackoverflow.com/questions/51722449/any-way-for-python-file-name-to-not-end-up-in-fully-qualified-name), see if there's anything you can use there. I was basically told what I'm expecting is unPythonic and that I should be satisfied with `mymod.tools.foo()`. – Amadan Jun 14 '19 at 10:10

3 Answers3

4

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.

user2357112
  • 260,549
  • 28
  • 431
  • 505
0

Try putting this in your __init__.py file:

from .tools import foo
del tools
Malekai
  • 4,765
  • 5
  • 25
  • 60
glenfant
  • 1,298
  • 8
  • 9
  • 2
    This technically works, but causes nasty side effects that outweigh any minor benefit of removing one name from the namespace. – user2357112 Jun 14 '19 at 10:14
0

You are not importing the tools module, it's just available when you import the package like you're doing:

import mymod

You will have access to everything defined in the __init__ file and all the modules of this package:

import mymod

# Reference a module
mymod.tools

# Reference a member of a module
mymod.tools.foo

# And any other modules from this package
mymod.tools.subtools.func

When you import foo inside __init__ you are are just making foo available there just like if you have defined it there, but of course you defined it in tools which is a way to organize your package, so now since you imported it inside __init__ you can:

import mymod

mymod.foo()

Or you can import foo alone:

from mymod import foo

foo()

But you can import foo without making it available inside __init__, you can do the following which is exactly the same as the example above:

from mymod.tools import foo

foo()

You can use both approaches, they're both right, in all these example you are not "cluttering the file" as you can see accessing foo using mymod.tools.foo is namespaced so you can have multiple foos defined in other modules.

Pierre
  • 12,468
  • 6
  • 44
  • 63
  • That's not what I want. I only want `mymod.foo()`, and because there are so many `foo`s, I want to put them in separate files instead of cramming them into `__init__.py`. – Nico Schlömer Jun 14 '19 at 10:43
  • @NicoSchlömer You don't have to import all of them into the `__init__` file, you can just import the ones that you want to use directly like `mymod.foo()`, the other ones you can import them or use them by referencing them like `mymod.submod.foo()`. By importing `mymod` you are not making all the submodules and their members available, you can just reference them when you need them. – Pierre Jun 14 '19 at 11:02
  • By "import" you mean copy-and-paste into it? No, there are too many, even the ones I want to make available to the outside. I must put them into separate files. – Nico Schlömer Jun 14 '19 at 11:08
  • @NicoSchlömer No I don't mean copy and paste, I mean using the `import` keyword `import whatever.you.want`, I suggest you check out some popular Python repositories on Github and see how the projects are organized. – Pierre Jun 14 '19 at 11:49