I know this is a 4-year-old question, but I found myself having to deal with this same problem recently and came up with a solution that seems to work.
This is the file structure we'll be using:
package/
__init__.py
import_me.py
import_me_no_re_export.py
The contents of package/import_me.py (used only for comparison) are as per the question:
import re
import sys
def hello():
pass
Those of package/import_me_no_re_export.py are:
def _():
global hello
import re
import sys
def hello():
pass
_()
del _
The following python CLI session (Python 3.8.5) shows the results of this approach:
>>> import package.import_me
>>> import package.import_me_no_re_export
>>> dir(package.import_me)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'hello', 're', 'sys']
>>> dir(package.import_me_no_re_export)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'hello']
>>> set(dir(package.import_me)) - set(dir(package.import_me_no_re_export))
{'sys', 're'}
As you can see, the import_me_no_re_export module does not re-export the sys and re modules.
Pros:
- It seems to work like a charm, no side effects whatsoever.
- Both
mypy and flake8 seem to like this approach alright.
Cons:
- At least Visual Studio Code seems to not like it too much (IntelliSense will simply not work and only suggest the deleted
_ function for a module so altered).
- You need to sacrifice an indentation level.
- Boilerplate
As other answers pointed out, embracing __all__ is probably the best bet, although it does not in fact address the issue; other than that, probably deal with the fact that these things are bound to happen.