Is it possible to import a single module from a Python package?

Viewed 116

I have a python package called utils. It consists of a few modules, and an __init__.py file tying them all together.

__init__.py:
    from .modA import util1
    from .modB import util2
    from .modC import util3

modA.py:
    def util1():
        print("Util 1")

modB.py:
    import flask               # <-- Note this import
    def util2():
        print("Util 2")

modC.py:
    def util3():
        print("Util 3")

When the software is running that imports this package, it's running in a virtual environment that has flask installed, so everything works fine.

The problem I'm having is that if I interactively run python in my shell, and do something like from utils.modC import util3, even though modC has absolutely no reliance on modB, and doesn't import, or use flask, I'm still going to get an error that modB couldn't import flask (assuming I didn't activate the virtual environment in my shell first).

$ python3
Python 3.9.0 (default, Oct 27 2020, 14:15:17) 
[Clang 12.0.0 (clang-1200.0.32.21)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from utils.modC import util3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/USERNAME/test/utils/__init__.py", line 2, in <module>
    from .modB import util2
  File "/Users/USERNAME/test/utils/modB.py", line 1, in <module>
    import flask
ModuleNotFoundError: No module named 'flask'

Without modifying the source code of the package, is there any way for me to import util3 without having flask in my sys.path (AKA without it trying to import everything listed in __init__.py)?

Failing that, is there any simple way to modify __init__.py to allow this?

1 Answers

As kaspersky pointed out here, you can do it in a dirty way:

>>> import sys
>>> sys.path.append("utils")
>>> from modC import util3
>>> util3()
Util 3

Or use the PYTHONPATH env variable to modify sys.path indirectly.

This works because when you run from modC import util3, Python will start looking through sys.path for a module or package named modC, which is different than when we ran from utils.modC import util3, where it was looking for a module or package named utils. Appending the string utils to sys.path tells it to look there as a last resort, and because there are no other places it will find modC, it will eventually find it there and load it as a module, rather than a package.

This apparently won't work in some cases, but works for me.

Related