pythonpython-3.xpython-import

Relative imports in Python 3


I want to import a function from another file in the same directory.

Usually, one of the following works:

from .mymodule import myfunction
from mymodule import myfunction

...but the other one gives me one of these errors:

ImportError: attempted relative import with no known parent package
ModuleNotFoundError: No module named 'mymodule'
SystemError: Parent module '' not loaded, cannot perform relative import

Why is this?


Solution

  • unfortunately, this module needs to be inside the package, and it also needs to be runnable as a script, sometimes. Any idea how I could achieve that?

    It's quite common to have a layout like this...

    main.py
    mypackage/
        __init__.py
        mymodule.py
        myothermodule.py
    

    ...with a mymodule.py like this...

    #!/usr/bin/env python3
    
    # Exported function
    def as_int(a):
        return int(a)
    
    # Test function for module  
    def _test():
        assert as_int('1') == 1
    
    if __name__ == '__main__':
        _test()
    

    ...a myothermodule.py like this...

    #!/usr/bin/env python3
    
    from .mymodule import as_int
    
    # Exported function
    def add(a, b):
        return as_int(a) + as_int(b)
    
    # Test function for module  
    def _test():
        assert add('1', '1') == 2
    
    if __name__ == '__main__':
        _test()
    

    ...and a main.py like this...

    #!/usr/bin/env python3
    
    from mypackage.myothermodule import add
    
    def main():
        print(add('1', '1'))
    
    if __name__ == '__main__':
        main()
    

    ...which works fine when you run main.py or mypackage/mymodule.py, but fails with mypackage/myothermodule.py, due to the relative import...

    from .mymodule import as_int
    

    The way you're supposed to run it is by using the -m option and giving the path in the Python module system (rather than in the filesystem)...

    python3 -m mypackage.myothermodule
    

    ...but it's somewhat verbose, and doesn't mix well with a shebang line like #!/usr/bin/env python3.

    An alternative is to avoid using relative imports, and just use...

    from mypackage.mymodule import as_int
    

    Either way, you'll need to run from the parent of mypackage, or add that directory to PYTHONPATH (either one will ensure that mypackage is in the sys.path module search path). Or, if you want it to work "out of the box", you can frob the PYTHONPATH in code first with this...

    import sys
    import os
    
    SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
    sys.path.append(os.path.dirname(SCRIPT_DIR))
    
    from mypackage.mymodule import as_int
    

    It's kind of a pain, but there's a clue as to why in an email written by a certain Guido van Rossum...

    I'm -1 on this and on any other proposed twiddlings of the __main__ machinery. The only use case seems to be running scripts that happen to be living inside a module's directory, which I've always seen as an antipattern. To make me change my mind you'd have to convince me that it isn't.

    Whether running scripts inside a package is an antipattern or not is subjective, but personally I find it really useful in a package I have which contains some custom wxPython widgets, so I can run the script for any of the source files to display a wx.Frame containing only that widget for testing purposes.