我需要在每个脚本中将我的项目目录添加到系统路径才能从另一个目录导入函数吗?

12

我正在尝试保持数据科学项目的良好组织,因此我在我的src目录中创建了一个名为utils的目录,其中包含一个名为helpers.py的文件,其中包含一些帮助函数,这些函数将用于许多脚本。如果我想要从完全不同的目录(例如src/processing/clean_data.py)导入src/utils/helpers.py中的func_name,应该采用什么样的最佳实践呢?

我看到了答案,并且我已经实现了一个有效的解决方案,但是这感觉很丑陋:

 sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))))

我这么做对吗?每个想要导入 func_name 的脚本都需要像 train_model.py 一样添加它吗?

我的当前项目文件夹结构:

myproject
    /notebooks
        notebook.ipynb
    /src
        /processing
            clean_data.py
        /utils
            helpers.py
        /models
            train_model.py
        __init__.py

示例文件:

# clean_data.py

import os
import sys

sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))))
from src.utils.helpers import func_name

func_name()


# helpers.py

def func_name():
    print('I'm a helper function.')

你只需要在每个导入/导出的层级中添加__init__.py文件。这是Python分发包的标准。 - KeatsKelleher
你应该在这些文件夹中的每个文件夹中都有一个 __init__.py 文件。并且你的导入应该基于你正在执行项目的位置,以避免所有路径操作。 - idjaw
@KeatsKelleher 在 /utils/processing 文件夹中添加 __init__.py 并移除 sys.path.append(...) 后,我遇到了 ModuleNotFoundError: No module named 'src' 的错误。您能否提供更详细的解答? - blahblahblah
停止使用sys.path。阅读提供的答案并了解Python包和模块的工作原理,以及导入系统的工作方式以及__init__.py的含义。 - idjaw
7个回答

9
正确的方法是使用__init__.pysetup.pysetuptools Python包:
myPackage/
    myPackage/
        __init__.py
    setup.py

这个链接包含了所有步骤。


8
首先,让我向您描述一下Python模块和Python包之间的区别,以便我们双方都了解。✌

模块是一个单独的.py文件(或多个文件),在一个导入中被导入并使用。✔

import aModuleName # 这里的'aModuleName'只是一个常规的.py文件。

相反,包是目录中模块的集合,它们给出了包层次结构。包含一个独特的__init__.py文件。✔

from aPackageName import aModuleName # 这里的'aPackageName'是一个带有`__init__.py`文件的文件夹 # 'aModuleName'只是一个常规的.py文件。


因此,当我们有一个名为proj-dir的项目目录,其结构如下⤵

proj-dir --|--__init__.py --package1 --|--__init__.py --|--module1.py --package2 --|--__init__.py --|--module2.py

请注意,我还将一个空的__init__.py添加到了proj-dir本身中,这也使其成为一个包。

现在,如果您想从package2module2中导入任何Python对象到package1module1中,则在文件module1.py中的import语句将是

from package2.module2 import object2 # 如果要导入整个module2,则为 from package2 import module2

我希望这个简单的解释能够澄清您对Python导入机制的疑虑并解决问题。如果没有,请在此处发表评论。


感谢您的详细回复。我已按照指示在目录中添加了__init__.py文件,但是当我运行projdir $ python package1/module1.py时,它返回了ModuleNotFoundError: No module named projdir - blahblahblah
我开始了一个新项目,与我的现有数据科学项目分开,使用您在答案中解释的确切目录/文件(只将proj-dir重命名为projdir)。您能否创建一个带有超级基本工作示例的Github存储库? - blahblahblah
@blahblahblah 另外,请检查我编辑的新项目目录结构,并将其与您的进行比较,不要忘记从所有模块中删除与 sys.path 相关的所有更改。 - Sushant Rajbanshi
我已将您添加为Github存储库的协作者。 - blahblahblah
1
@blahblahblah 你们能分享一下这个问题的解决方案吗? - Homero Esmeraldo
显示剩余2条评论

2
首先,让我澄清一件事,如果你只需要使用其中的一部分,那么导入整个模块并不是一个好主意。相反,您可以使用 from 来仅导入库/包下的特定函数。通过这样做,您可以在内存和性能方面使程序更加高效。
要了解更多,请参考以下内容:
  1. 'import module' or 'from module import'
  2. difference between import and from
现在让我们看看解决方案。
在开始解决方案之前,让我澄清一下__init__.py 文件的用途。它只告诉python解释器,该目录下的*.py文件可导入,也就是它们是模块且可能是包的一部分。
因此,如果您有N个子目录,则必须在所有这些子目录中放置 __init__.py 文件,以便它们也可以被导入。在 __init__.py 文件中,您还可以添加一些其他信息,例如应包含哪些路径、默认函数、变量、作用域等。要了解这些信息,请通过谷歌搜索 __init__.py 文件或查看一些Python库中的相同 __init__.py 文件。
更多信息:
  1. modules
  2. Be pythonic
因此,正如@Sushant Chaudhary所述,您的项目结构应如下:
proj-dir
 --|--__init__.py
   --package1
     --|--__init__.py
     --|--module1.py
   --package2
     --|--__init__.py
     --|--module2.py

如果我像上面那样将__init__.py文件放在我的目录下,它是否可以被导入并正常工作呢?是和否。
是: 如果您在项目/包目录内导入模块。例如,在您的情况下,您正在将package1.module1导入到pakage2.module2中,如from package1 import module1。 在这里,您必须在子模块中导入基本目录,为什么?如果您从相同位置运行模块,则项目将正常运行即:在package2中作为python module2.py,但如果您从其他目录运行模块,则会抛出ModuleNotFoundError即:任何路径,除了在package2下面的路径之外,例如在proj-dir下的 python package2/module2.py 。这就是您的情况所发生的事情,您正在从project-dir中运行该模块。
如何解决这个问题?
您需要在module2.py中将basedir路径附加到系统路径中:
from sys import path
dir_path = "/absolute/path/to/proj-dir"
sys.path.insert(0, dir_path)

这样module2就能够找到package1(以及其中的module1)。

2- 您需要在proj-dir的__init__.py文件中添加所有子模块路径。

例如:

#__init__.py under lxml
# this is a package

def get_include():
    """
    Returns a list of header include paths (for lxml itself, libxml2
    and libxslt) needed to compile C code against lxml if it was built
    with statically linked libraries.
    """
    import os
    lxml_path = __path__[0]
    include_path = os.path.join(lxml_path, 'includes')
    includes = [include_path, lxml_path]

    for name in os.listdir(include_path):
        path = os.path.join(include_path, name)
        if os.path.isdir(path):
            includes.append(path)

    return includes

这是 lxml__init__.py文件(一个用于解析html、xml数据的python库)。您可以参考任何带有子模块的Python库中的__init__.py文件。例如(os,sys)。我提到了lxml,因为我认为这样更容易让您理解。您甚至可以查看其他库/包下的__init__.py文件。每个文件都有自己定义子模块路径的方式。
如果您想在目录外导入模块,则必须将模块路径导出,以便其他模块可以在环境变量中找到它们。这可以通过将基本目录的绝对路径附加到PYTHONPATHPATH来直接完成。
要了解更多信息,请参见:
  1. 操作系统中的PATH变量
  2. PYTHONPATH变量
因此,为了解决您的问题,请在proj-dir下的__init__.py文件中包含所有子模块的路径,并将/absolute/path/to/proj-dir添加到PYTHONPATHPATH中。 希望这个答案可以解释关于__init__.py的用法并解决您的问题。

这不是缺少了get_include()被调用的上下文吗? - Homero Esmeraldo
你需要在module1.py中将basedir路径添加到系统路径中,这样它才能找到module2.py,不是吗?但如果这样做的话,这恰恰是OP试图避免的,对吧? - Homero Esmeraldo
@HomeroBarrocasSEsmeraldo 我提到这是 lxml__init__.py 文件。因此,每当自动导入 lxml 时,都会调用 __init__.py。您可以将其视为包 lxml 的构造函数。有关更多详细信息,请参阅 lxml 文档。 - Mani
该函数未在任何地方调用,但被添加在那里以便子模块可以直接调用该函数以包含项目路径。欲了解更多,请参考此链接:https://dev59.com/kHRB5IYBdhLWcg3w-8A9#50307979。 - Mani
我看了你的链接,顺便提一下。但我认为这正是 OP 要避免的。 - Homero Esmeraldo
显示剩余2条评论

1
在Linux上,您只需将src目录的父文件夹路径添加到~/.local/lib/python3.6/site-packages/my_modules.pth中。请参见Using .pth files。然后,您可以从系统上的任何位置导入src模块。
注意1:将python3.6替换为您想要使用的任何Python版本。
注意2:如果您使用Python2.7(不知道其他版本),则需要在src/src/utils中创建__init__.py(为空)文件。
注意3:任何名称为.pth的文件都可以用于my_modules.pth

0

是的,您只能从已安装的软件包或工作目录或子目录中的文件导入代码。


0

我认为,如果您安装了您的模块或包,就像安装和导入其他包一样(如numpy、xml、json等),您的问题将得到解决。

我在所有项目中都经常使用一个包ulitilies,我知道导入它很麻烦。

以下是如何打包Python应用程序以使其可pip安装的说明: https://marthall.github.io/blog/how-to-package-a-python-app/


0
  • 进入你的Python安装文件夹

  • 进入lib目录

  • 进入site-packages目录

  • 创建一个名为any_thing_you_want.pth的新文件

  • 使用你喜欢的文本编辑器在该文件中键入.../src/utils/helpers.py

注意:在scr/utils/helpers.py之前的省略号将看起来像:C:/Users/blahblahblah/python_folders/scr... <- 你需要这个!

这是一种简单易行的方法,可以保持代码的整洁性,复杂程度最低。缺点是,对于你的模块所在的每个文件夹,都需要创建example.pth文件。优点:适用于Windows系统,从Windows 10开始。


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