Python - ModuleNotFoundError: 找不到名为的模块

36

我是Python的新手,在这个简单的例子中遇到了以下错误:

这是我的项目结构:

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

当我执行src/main.py文件时,出现了以下错误:

☁  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

如果我将main.py文件移动到根目录,然后再次执行该文件,它就可以正常工作...但是在src/目录中无法正常工作。
这是我的main.py:
from lib import my_custom_lib

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

do_something('Hello World!')

注意:当我从Pycharm执行相同的代码时,它可以正常工作,但是从我的终端无法正常工作。


我可以问一下为什么lib(即代码)在src之外吗?您说它是自定义的,它在项目之间共享吗? - progmatico
嗨@progmatico,在我的真实项目中,我有/src目录和同级别的/tests。问题是当我执行测试时,它试图从/src导入代码。 - Nicolas
2
如果您在python_project文件夹及其每个子文件夹下放置一个空的__init__.py文件,那么您应该能够从项目文件夹包的任何位置进行导入。 - progmatico
如果在项目之间共享了库,你可以使用 virtualenv 并在其中安装这些依赖项,或将共享的库放置在与项目文件夹并排的文件夹中,并在包入口处内部附加这些文件夹到 sys.path。 - progmatico
8个回答

32

如果执行的脚本位于目录src中,则您的PYTHONPATH设置为执行脚本的父目录。因此,它永远无法找到同级目录lib,因为它不在路径内。有几个选择;

  • 如果lib /属于您的代码,请将其移动到src /中。如果它是外部包,则应使用pip安装。
  • src /之外具有顶级脚本,导入并运行src.main。这将添加顶级目录到python路径。
  • src/main.py中修改sys.path以包括顶级目录。通常不建议这样做。
  • 使用python -m src.main以模块形式调用src/main.py,它将向python路径添加顶级目录。输入有点麻烦,而且您需要更改所有导入。

嗨@MarkM,谢谢!我明白了。也许我做错了,因为我的项目有my_project/src/my_project/tests/目录。当我执行测试时,问题就出现了。有没有其他方法来构建Python项目以避免这个问题? - Nicolas
2
不需要重新组织你的项目,将测试放在源代码之外是一个好的实践!如果srctests都有一个__init__.py文件,并且假设你正在编写传统的unittest.TestCase测试,那么你可以利用标准的unittest模块来发现并通过从顶层目录运行简单的python -m unittest命令来运行你的测试。 - MarkM
谁是Henrique Branco? - John
我猜测有人在这个问题下发布了一个(现已删除的)回复/评论。我会删除答案中的那部分内容。 - MarkM
如何正确设置Visual Studio Code的PYTHONPATH:更详细的说明请参考以下链接:https://dev59.com/I1QJ5IYBdhLWcg3wiWab - FrankyHollywood

12
如果我可以补充MarkM的答案,如果你想保留当前的目录结构并使其正常工作,你可以在根目录中添加一个setup.py文件,你可以使用setuptools创建一个可安装的包。
如果你的文件有以下内容:
# 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',
    ],
  },
)

然后你执行pip install [--user] -e path/to/directory,你会得到一个"可编辑的包",它实际上是一个指向开发目录中的包的符号链接,因此你所做的任何更改都不需要重新安装(除非你重新调整包的结构或添加/删除/编辑入口点)。
这假设你的src/main.py有一个主函数。
你还需要在你的"包"目录中添加__init__.py文件,即使在Python 3中,否则Python会假设这些是命名空间包(我不会详细解释),find_packages()调用将无法找到它们。
这也将使你的相对导入起作用。绝对导入只有在从入口点调用脚本时才起作用,而在直接在开发目录中调用脚本时不起作用。

什么是“ejig”?“existing”?还是其他什么? - undefined
@PeterMortensen 在你编辑之前的文本是正确的:“you rejig”与“your ejig”。请查找一下“rejig”这个词的定义。 - undefined

6

你应该将main.py脚本放在目录结构中所有Python软件包的上方。尝试更新您的项目到以下结构:

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

之后,在你的项目目录中运行python main.py即可。


3

你在错误地使用 from a import b。正确的写法应该是:

import lib.my_custom_lib

另一种方法是从模块中导入特定的方法、函数和类,而不是整个模块。要从my_custom_lib模块中导入特定的函数,代码如下:
from lib.my_custom_lib import foo

1
在 main.py 中,我将 "from lib import my_custom_lib" 更改为 "import lib.my_custom_lib",但仍然出现相同的错误:"ModuleNotFoundError: No module named 'lib'"。 - Nicolas
请确保在您的代码中将函数引用为 lib.my_custom_lib.show(message) - Holden
这是我现在的代码,但我遇到了同样的问题:`import lib.my_custom_libdef do_something(message): lib.my_custom_lib.show(message)do_something('Hello World!')` - Nicolas
当我从Pycharm执行相同的代码时,它可以正常工作,但从终端无法正常工作。 - Nicolas

2
尝试使用相对导入代替: from ..lib import my_custom_lib

2
导入错误:尝试相对导入,但没有已知的父包 :( - Nicolas
尝试跟随这个教程,它可能会有所帮助 https://napuzba.com/a/import-error-relative-no-parent - Edgardo Obregón

1

在我的Visual Code中,实际错误出现在第一行。

我没有导入_typeshed模块,但默认情况下它存在。因此,如果你在第一行发现该模块,请删除它。


1
请在您的回答中提供更多细节。目前的写法让人难以理解您的解决方案。 - Community
1
这并没有回答问题。一旦您拥有足够的声望,您将能够评论任何帖子;相反,提供不需要询问者澄清的答案 - EDS

0

对于我来说,在这种情况下,使用显式路径导入最好。如果您需要向上跳转到树的上一级,请使用'..'。虽然有些繁琐,但它总是有效的。

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()

0

MarkM的答案仍然很棒; 我现在正在使用PyPi。我试图消除他对“当前目录”的说法中的歧义。如果我的困惑是一个有意的特性,这里是我是如何做到的。

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

script1.py在两个目录中是相同的:

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

无论我从哪里运行它,PYTHONPATH 都会在指定脚本的目录前面添加路径:
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']

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接