从子目录导入文件?

643

我有一个名为tester.py的文件,位于/project目录下。

/project目录下有一个子目录lib,其中包含一个名为BoxTime.py的文件:

/project/tester.py
/project/lib/BoxTime.py

我想从tester导入BoxTime。 我尝试过以下方法:

import lib.BoxTime

结果为:

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

有什么想法可以从子目录导入BoxTime?

编辑

__init__.py是个问题,但别忘了将BoxTime称为lib.BoxTime,或使用:

import lib.BoxTime as BT
...
BT.bt_function()

1
似乎他正在将 lib/BoxTime 导入到 tester 中。 - ndemarco
13个回答

687

83
为什么感觉有点“hacky”?这是因为Python标记安全/可用导入目录的方式。 - IAbstract
12
它不仅标识了安全/可用的导入目录,还提供了一种在导入目录名称时运行某些初始化代码的方法。 - Sadjad
55
是的,这种方法很“hacky”(指粗糙、不够优雅),甚至有些“dirty”(指有些不道德或者欺骗性),我认为语言不应强制要求使用其自身的方式在文件系统中加载文件。在PHP中,我们通过让用户注册多个自动加载函数来解决这个问题,当命名空间/类缺失时它们会被调用。然后社区制定了PSR-4标准,并由Composer实现,如今没有人需要担心这个问题。也不需要愚蠢的硬编码“__init__”文件了(但如果你想要,只需注册一个自动加载钩子!这就是“hacky”和“hackable”之间的区别)。 - Morgan Touverey Quilling
1
@YaShChaudhary 直到我将引用更改为库:文件夹名称.模块名称,它才对我起作用。 - mimic
3
必要的 __init__.py 文件至少不舒服,无论是 hacky 还是不 hacky。这一点在 Python 3.3 中得到了解决:一个包可以在没有该文件的情况下被创建。 - MasterControlProgram
显示剩余3条评论

221
  • 创建一个名为lib的子目录。
  • 创建一个名为lib\__init__.py的空文件。
  • lib\BoxTime.py中编写一个如下所示的函数foo()

  • def foo():
        print "foo!"
    
  • 在位于lib目录上级目录的客户端代码中,编写以下内容:

  • from lib import BoxTime
    BoxTime.foo()
    
    运行您的客户端代码。您将得到:
    foo!
    

很长时间之后,在Linux中看起来会像这样:

% cd ~/tmp
% mkdir lib
% touch lib/__init__.py
% cat > lib/BoxTime.py << EOF
heredoc> def foo():
heredoc>     print "foo!"
heredoc> EOF
% tree lib
lib
├── BoxTime.py
└── __init__.py

0 directories, 2 files
% python 
Python 2.7.6 (default, Mar 22 2014, 22:59:56) 
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from lib import BoxTime
>>> BoxTime.foo()
foo!

2
你能提供一个 Python 文档的链接,解释一下这个吗?谢谢! - Zenon
5
让我们把这个链接变成可点击的:https://docs.python.org/3/tutorial/modules.html#packages - Gabriel Staples
@hughdbrown - 我在谈论使用 "from" 参数可能会起作用,也可能不起作用的情况。 - Alexander Stohr
前导零是无效的。例如,像“000abc.py”这样的文件名不起作用。 - Charizard_knows_to_code
请注意将文件命名为BoxTime.py,而不是boxtime.py等。如果您使用“from lib import BoxTime”导入文件,请确保文件名相应地命名,即使我的类名为BoxTime,但未按照此方式命名文件时,导入也会失败。 - ESP32
显示剩余5条评论

100

您可以尝试将其插入到sys.path中:

sys.path.insert(0, './lib')
import BoxTime

16
如果你因某些原因无法或不想创建 init.py 文件,那么这很棒。 - jpihl
5
如果你从“project”目录下运行Python,它就会有效。点号“.”的解释是相对于你当前工作的目录而不是你执行文件所在的目录。比如你用命令cd /data进入该目录,然后运行命令python ../project/tester.py,那么它将不会起作用。 - morningstar
2
这对我很有效。我更喜欢这种方法而不是使用init.py文件,因为它可以使导入语句更加清晰。 - Taylor Evanson
9
这种方法要好得多,也是“正确”的解决方案。init.py 会使像 boto 这样有自己子文件夹和模块的包出现问题。 - Dave Dopson
1
@jpihl 你需要创建(至少)一个名为***init.py***的空文件,以允许Python从该文件夹导入模块。我已经尝试过这个解决方案,它在v2.7.6上完美运行。 - m3nda
显示剩余4条评论

53

我写下这篇文章是因为有人似乎认为你必须创建一个lib目录。

你不需要把你的子目录命名为lib,你可以用任何名称来命名,只要你在其中加入一个__init__.py文件。

你可以通过在Linux shell中输入以下命令来实现:

$ touch anything/__init__.py 

现在你有了这个结构:

$ ls anything/
__init__.py
mylib.py

$ ls
main.py

然后您可以像这样将mylib导入到main.py中:

from anything import mylib 

mylib.myfun()

您也可以像这样导入函数和类:

from anything.mylib import MyClass
from anything.mylib import myfun

instance = MyClass()
result = myfun()

你放置在 __init__.py 中的任何变量、函数或类也都可以被访问:

import anything

print(anything.myvar)

或者像这样:
from anything import myvar

print(myvar)

2
我的文件夹结构是 utils\__init__.pyutils\myfile.py。(Utils 包含这两个文件)我尝试使用 from utils.myfile import myMethod 导入,但是我得到了 ModuleNotFoundError: No module named 'utils' 的错误。可能出了什么问题?附言: 我正在使用 Django 并尝试在与 utils 文件夹相同级别的 views.py 中导入。 - Jagruti
在导入模块时可以使用绝对路径,并使用 PYTHONPATH=. python path/to/program.py 运行程序。 - nurettin
最清晰的答案!谢谢 - Ryan Loggerythm

28

尝试使用import .lib.BoxTime。有关相对导入的更多信息,请阅读PEP 328


2
我不认为我曾经见过那种语法的使用。是否有强烈的理由(不)使用这种方法? - tgray
2
为什么这不是答案呢?当然,如果你想要使用整个包的方式,那么你应该这样做。但这不是原问题所在。 - Travis Griggs
这个错误提示是:ValueError: 在非包中尝试使用相对导入。 - Alex
6
只有当你要导入的文件本身是包的一部分时,这才有效。否则,你将会收到 @Alex 指出的错误提示。 - Jonathon Reinhart
尝试在Python 3中执行以下操作会导致 SyntaxError: invalid syntax。PyLance告诉我“相对导入不能与"import .a"格式一起使用;请改为使用"from . import a"。” 在这种情况下,应该使用 from .lib import BoxTime - qrsngky

20

完整的示例已包含在内

这基本上涵盖了所有情况(请确保相对路径/to/your/lib/folder中有__init__.py):

import sys, os
sys.path.append(os.path.dirname(os.path.realpath(__file__)) + "/relative/path/to/your/lib/folder")
import someFileNameWhichIsInTheFolder
...
somefile.foo()

例子:

你在项目文件夹中有:

/root/myproject/app.py

您在另一个项目文件夹中有:


/root/anotherproject/utils.py
/root/anotherproject/__init__.py

您想要使用/root/anotherproject/utils.py中的foo函数。

因此,在app.py中编写如下代码:

import sys, os
sys.path.append(os.path.dirname(os.path.realpath(__file__)) + "/../anotherproject")
import utils

utils.foo()

2
如果你正在使用 os.path,那么你可能想要使用 os.path.join((os.path.dirname(os.path.realpath(__file__)),'..','anotherproject') 来代替在路径拼接中硬编码 / - cowbert
为什么你不能直接使用"../anotherproject"而不需要使用os.path.dirname()呢? - Moshe Rabaev
@MosheRabaev - 使用os.path函数是一个好的编程习惯。如果写入"../anotherproject"并将代码移动到Windows操作系统中,代码将会出错!os.path工具知道如何返回正确的路径,考虑到代码运行的操作系统。更多信息请参见https://docs.python.org/2/library/os.path.html - Mercury
1
如果您使用“..”而没有使用dirname(realpath(__file__)),那么它将计算相对于运行脚本时的当前工作目录的路径,而不是相对于脚本所在位置的路径。@MosheRabaev - TJ Ellis

20
你的lib目录中是否包含 __init__.py 文件?
Python使用 __init__.py 来确定一个目录是否为模块。

8
在子目录/lib中创建一个名为__init__.py的空文件,并在主代码开头添加以下内容。
from __future__ import absolute_import 

那么

import lib.BoxTime as BT
...
BT.bt_function()

或者更好。
from lib.BoxTime import bt_function
...
bt_function()

这是我在Pycharm中唯一有效的正确修复方法,谢谢。 - CalfCrusher

6

这里是对之前答案的补充。

如果你想从所有子目录中导入所有文件,可以将以下代码添加到文件根目录。

import sys, os
sys.path.extend([f'./{name}' for name in os.listdir(".") if os.path.isdir(name)])

然后您可以像操作当前目录下的文件一样,从子目录中导入文件。

工作示例

如果我在项目中有以下带有子目录的目录...

.
├── a.py
├── b.py
├── c.py
├── subdirectory_a
│   ├── d.py
│   └── e.py
├── subdirectory_b
│   └── f.py
├── subdirectory_c
│   └── g.py
└── subdirectory_d
    └── h.py

我可以将以下代码放入我的 a.py 文件中。
import sys, os
sys.path.extend([f'./{name}' for name in os.listdir(".") if os.path.isdir(name)])

# And then you can import files just as if these files are inside the current directory

import b
import c
import d
import e
import f
import g
import h

换句话说,这段代码将会抽象出文件来自哪个目录的信息。

1
嘿!我修改了你的一行代码,现在它变成了 sys.path.extend([f'{item[0]}' for item in os.walk(".") if os.path.isdir(item[0])]),现在可以从项目目录递归地进行爬取。感谢你的回答!非常有用。 - Shrout1
1
@Shrout1,干得好! - Victor

2

以下是这个文件夹层次结构图的示例:

/project/tester.py    
/project/lib/BoxTime.py

1- 在 lib 文件夹中创建一个名为 __init__.py 的空 py 文件。

2- 在调用的 py 文件 tester.py 中添加以下代码行。

import os, sys
sys.path.insert(0,'lib')# insert the folder lib in system path
from BoxTime import Function_name # from the py file import the needed function

这里可以找到易懂的解释。

注意:这被称为在不同文件夹中创建/导入模块。

个人经验:我尝试从jupyter笔记本创建模块,但它没有起作用(可能是我使用.ipynb时没有正确操作),我需要在juypyter笔记本之外手动创建,或使用其他IDE(如pycharm)。


sys.path.insert 对我来说非常关键。谢谢! - Greg Biles

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