33

I'm new in Python and I'm having the following error with this simple example:

This is my project structure:

python_project
.
├── lib
│   ├── __init__.py
│   └── my_custom_lib.py
└── src
    ├── __init__.py
    └── main.py

And this is the error when I execute the src/main.py file:

☁  python_project  python src/main.py

Traceback (most recent call last):
  File "src/main.py", line 3, in <module>
    from lib import my_custom_lib
ImportError: No module named lib

If I move the main.py file to the root and then I execute this file again, it works... But it is not working inside src/ directory.

This is my main.py:

from lib import my_custom_lib

def do_something(message):
        my_custom_lib.show(message)

do_something('Hello World!')

Note: When I execute the same code from Pycharm, it is working fine, but not from my terminal.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Nicolas
  • 331
  • 1
  • 3
  • 5
  • May I ask why is lib, that is code, outside src? You say it is custom, is it shared between projects? – progmatico Apr 30 '20 at 21:07
  • Hi @progmatico in my real project I have the `/src` directory and in the same level `/tests`. The problem is when I execute a test, it's trying to import the code from `/src`. – Nicolas Apr 30 '20 at 23:42
  • 2
    If you put an empty `__init__.py` under python_project folder, and every subfoder, you should be able to import from everywhere inside the project_folder package. – progmatico May 01 '20 at 22:00
  • If you have shared libs between projects, you can either use a virtualenv and install those dependencies in it or place the shared libs in folders side by side with the project folder and append the folders into the sys.path from iniside your package entry point. – progmatico May 01 '20 at 22:02

8 Answers8

30

Your PYTHONPATH is set to the parent directory of the executed script. So if the executed script is inside a directory src, it will never be able to find the sister directory lib because it isn't within the path. There's a few choices;

  • Just move lib/ into src/ if it belongs to your code. If it's an external package, it should be pip installed.
  • Have a top-level script outside of src/ that imports and runs src.main. This will add the top-level directory to python path.
  • In src/main.py modify sys.path to include the top-level directory. This is usually frowned upon.
  • Invoke src/main.py as a module with python -m src.main which will add the top-level directory to the python path. Kind of annoying to type, plus you'll need to change all your imports.
MarkM
  • 798
  • 6
  • 17
  • Hi @MarkM, thank you! I understand. Maybe I'm doing wrong because I have a project with `my_project/src/` and `my_project/tests/` directories basically. The problem comes when I execute a test. Is there any other way to structure a project in Python to avoid this problem? – Nicolas Apr 30 '20 at 21:44
  • 2
    No need to restructure your project, having tests outside your source is good practice! If both `src` and `tests` have a `__init__.py`, and assuming you're writing traditional `unittest.TestCase` tests, you can leverage the standard unittest module to [discover](https://docs.python.org/3/library/unittest.html#test-discovery) and run your tests with a simple `python -m unittest` from the top-level directory. – MarkM May 01 '20 at 21:21
  • Who is Henrique Branco? – John Jul 28 '22 at 11:29
  • I guess someone who posted a (now deleted) reply/comment to this question. I'll remove that part of the answer. – MarkM Jul 29 '22 at 17:54
  • more detailed explanation on how to set the PYTHONPATH: https://stackoverflow.com/questions/53653083/how-to-correctly-set-pythonpath-for-visual-studio-code – FrankyHollywood Sep 07 '22 at 15:25
12

If I may add to MarkM's answer, if you wanted to keep your current directory structure and still make it work, you could add a setup.py in your root dir, where you can use setuptools to create a package you could install.

If your file had something along the lines of:

# setup.py

from setuptools import find_packages, setup

setup(
  name='foo',
  version=`1.0.0`,
  packages=find_packages(),
  entrypoints={
    'console_scripts': [
      'foo=src.main:main',
    ],
  },
)

And then you do pip install [--user] -e path/to/directory you'll get an "editable package" which will effectively a symlink to the package in your development directory, so any changes you make will not require a reinstall (unless of course you rejig package structure or add/remove/edit entry points).

This does assume your src/main.py has a main function.

You'll also need __init__.py files in your "package" directories, even in Python3, as otherwise Python assumes these are namespace packages (Won't go into detail) and the find_packages() call won't find them.

This will also allow your relative imports to work. Absolute imports will only work when invoking the script from your entry point but not when calling the script directly in your development directory.

Pesho_T
  • 814
  • 1
  • 6
  • 18
6

You should have your main.py script above all python packages in your directory structure. Try to update your project to the following structure:

.
|__ main.py
|__ lib
|   |__ __init__.py
|   |__ your_custom_lib.py
|__ another_python_package
    |__ __init__.py
    |__ another_python_scripts

After that, python main.py in your project directory will work.

bednarb
  • 89
  • 1
  • 6
3

You are using the from a import b incorrectly. It should look like this:

import lib.my_custom_lib

The other method is used to import certain methods, functions, and classes from a module, not the module itself. To import a specific function from the my_custom_lib module, it would look like this:

from lib.my_custom_lib import foo
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Holden
  • 632
  • 4
  • 15
  • 1
    In the main.py I changed "from lib import my_custom_lib" for "import lib.my_custom_lib" but I'm still having the same error: "ModuleNotFoundError: No module named 'lib'" – Nicolas Apr 30 '20 at 20:47
  • make sure that in your code you reference the function as `lib.my_custom_lib.show(message)` – Holden Apr 30 '20 at 20:50
  • this is my code right now, but I have the same problem: `import lib.my_custom_lib def do_something(message): lib.my_custom_lib.show(message) do_something('Hello World!')` – Nicolas Apr 30 '20 at 20:53
  • When I execute the same code from Pycharm is working fine, but not from my terminal. – Nicolas Apr 30 '20 at 20:55
2

Try using a relative import instead:

from ..lib import my_custom_lib

1

in my case in visual code actual error in line 1

I didn't imported _typeshed module but by default it was their so delete that module if you found in line 1

  • 1
    Please provide additional details in your answer. As it's currently written, it's hard to understand your solution. – Community Sep 06 '21 at 02:39
  • 1
    This does not provide an answer to the question. Once you have sufficient [reputation](https://stackoverflow.com/help/whats-reputation) you will be able to [comment on any post](https://stackoverflow.com/help/privileges/comment); instead, [provide answers that don't require clarification from the asker](https://meta.stackexchange.com/questions/214173/why-do-i-need-50-reputation-to-comment-what-can-i-do-instead). – EDS Sep 06 '21 at 03:59
0

MarkM's answer is still excellent; I'm using PyPi now. I attempted to disambiguate what he was saying about "current directory". In case my confusion was an intended feature, here is how I did it.

vagrant@testrunner:~/pypath$ tree
.
├── proga
│   └── script1.py
└── progb
    └── script1.py

script1.py is the same in both directories:

#!/usr/bin/env python3
import sys
print(sys.path)

Where I run it from makes no difference, PYTHONPATH prepends the directory containing the script I specify:

vagrant@testrunner:~/pypath/proga$ ./script1.py 
['/home/vagrant/pypath/proga', '/usr/lib/python38.zip', '/usr/lib/python3.8', '/usr/lib/python3.8/lib-dynload', '/home/vagrant/.local/lib/python3.8/site-packages', '/usr/local/lib/python3.8/dist-packages', '/usr/lib/python3/dist-packages']
vagrant@testrunner:~/pypath/proga$ ../progb/script1.py 
['/home/vagrant/pypath/progb', '/usr/lib/python38.zip', '/usr/lib/python3.8', '/usr/lib/python3.8/lib-dynload', '/home/vagrant/.local/lib/python3.8/site-packages', '/usr/local/lib/python3.8/dist-packages', '/usr/lib/python3/dist-packages']
John
  • 6,433
  • 7
  • 47
  • 82
0

For me importing with explicit paths works best in such situations. If you need to go up in the tree use '..'. The plumbing is a bit cumbersome, but it always works.

path = os.path.abspath(os.path.join(pathlib.Path(__file__).parent.absolute(), '..', 'subdir', 'myFile.py'))
loader = importlib.machinery.SourceFileLoader('myFile', path)
spec = importlib.util.spec_from_loader('myFile', loader)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)

# now use the module:
module.myMethod()
myClassInstance = module.myClass()
FrankyHollywood
  • 1,497
  • 19
  • 18