Search code examples
pythonpython-3.xpython-importimporterror

How to structure relative imports in Python 3.7?


I've found myself in an import error situation I thought I was well past. I'm running Python 3.7.4 in VSCode on Windows 10. Without any further ado.. this is my folder and file structure, starting with folder crypto_code:

/crypto_code:

__init__.py (blank)

/crypto_driver:
    __init__.py (blank)
    crypto_balances.py

/crypto_exchg:
    __init__.py (blank)
    exchg_lens.py
    bittrex_lens.py
    coinbase_lens.py

Top of file coinbase_lens.py:

import exchg_lens

Top of file crypto_balances.py:

import sys
sys.path.append('..')
from crypto_exchg import coinbase_lens

Running coinbase_lens as main imports exchg_lens without error.

Running crypto_balances.py seems to recognize the paths/structure, but throws 'ModuleNotFoundError': No module named 'exchg_lens'.

Any help would be greatly appreciated. Thanks in advance.


Solution

  • This is an issue I've found troublesome as well. I have a setup that works and avoid messing with it. However this is good practice (for me) so ...

    As a starting position I suggest abandoning import sys and sys.path.append. These are hacks that obscure the issue. If you use a linter (flake8 is what I use) then it will complain about sys.path.append appearing before imports and it is worth respecting what the linter dislikes.

    I think the starting issue is, are you writing a module or a program. Restructuring your folders to provide a clear distinction.

    /
    program.py
    /crypto
        __init__.py (empty)
        /driver
            __init__.py (empty)
            crypto_balances.py
        /exchg
            __init__.py (empty)
            coinbase_lens.py
            exchg_lens.py
        /test
            __init__.py (empty)
            test_coinbase_lens.py
    

    program.py

    from crypto.driver import crypto_balances
    from crypto.exchg import coinbase_lens, exchg_lens
    

    crypto_balances.py

    from ..exchg import coinbase_lens
    print('crypto_balances')
    

    coinbase_lens.py

    from . import exchg_lens
    print('coinbase_lens')
    

    exchg_lens.py

    print('exchg_lens')
    

    When run from the / folder produces

    python .\program.py
    exchg_lens
    coinbase_lens
    crypto_balances
    

    Of course you should have a test framework to run tests on your module. The test file has the following content. Pytest sucessfully finds (and runs) the following test.

    from crypto.exchg import coinbase_lens
    
    def test():
        assert True
    

    Later ... you can look at the package __init__.py and adding the imports and __all__ there to allow namespace management. Easy enough to add later.

    Edited to add

    Feels like I am missing a useful point that is worth including. \crypto\__init__.py and program.py can be altered to allow you to manage how the namespace of the module appears to the program.

    __init__.py becomes

    from crypto.driver import crypto_balances
    from crypto.exchg import coinbase_lens, exchg_lens
    __all__ = [
        "crypto_balances",
        "coinbase_lens", "exchg_lens"
    ]
    

    program.py is now ...

    from crypto import crypto_balances
    from crypto import coinbase_lens, exchg_lens