how to make __main__ a package -OR- script and a package submodule at the same time

Question:

I have this structure:

app.py
sub/
    __init__.py
    lib.py
    utils.py

In app.py, I want to be able to do sth like this:

from sub.lib import some_func

In lib.py, I want to be able to import utils.py.

And I also want that it’s possible to execute lib.py as a script directly. (I.e. cd sub; python3 lib.py.)

The problem is, if I start lib.py as a script, it will not be a package, and thus, it cannot use relative imports, so sth like from .utils import some_util_func would not work. However, if I import lib.py as part of the sub package, only the relative import will work.

How do I solve this? (In an easy way. E.g. without creating another small wrapper script just to call lib.py.)

E.g. if there is a way to mark the __main__ module as a package, it would solve that. But how? Is this possible? E.g. by defining __path__ or so?

Asked By: Albert

||

Answers:

Ok, actually, my last comment really worked. If I add this in the beginning of lib.py, it seems to work:

import os

if __name__ == '__main__':
    __path__ = [os.path.dirname(os.path.abspath(__file__))]

(About __path__, see e.g. here.)

Edit With newer Python versions (3.9), this does not seem to be enough anymore. See below for an extended version.


Also, in PEP 366 it is mentioned:

When the main module is specified by its filename, then the __package__ attribute will be set to None. To allow relative imports when the module is executed directly, boilerplate similar to the following would be needed before the first relative import statement:

if __name__ == "__main__" and __package__ is None:
    __package__ = "expected.package.name"

I didn’t tried this yet.


For Python 3.9, I needed to extend my solution:

if __name__ == '__main__':
    # Make relative imports work here. https://stackoverflow.com/questions/54576879/
    __path__ = [os.path.dirname(os.path.abspath(__file__))]
    __package__ = "YOUR_CUSTOM_PACKAGE_NAME"
    pkg_mod = types.ModuleType(__package__)
    sys.modules[__package__] = pkg_mod
    pkg_mod.__package__ = __package__
    pkg_mod.__file__ = os.path.abspath("__init__.py")
    pkg_mod.__path__ = __path__
Answered By: Albert

You can set PYTHONPATH environment variable when starting your program.

Just type from lib directory:

PYTHNONPATH="path/to/root/directory" python lib.py
Answered By: butusk0
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.