从父目录导入Python,并保持符合flake8规范

6

这个导入工作正常,但在某些方面感觉不太合适。主要问题是在切片中使用了特定的数字来获取父级路径,并且会干扰 flake8 linter。

import os
import sys
sys.path.append(os.path.dirname(__file__)[:-5])
from codeHelpers import completion_message

这是一个类似于以下格式的文件系统:

parent_folder
    __init__.py
    codeHelpers.py

    child_folder
        this_file.py

child_folder实际上被称为week1,因此在片段中出现了数字5)
这个问题与Python从父目录导入非常类似,但在那种情况下,讨论重点集中在是否从终点运行测试。 在我的情况下,我有一系列的目录,其中包含使用位于父级中的帮助程序的代码。 背景:每个目录都是一组每周的练习,因此我希望尽可能简单。
是否有更干净、更pythonic的方法来执行此导入?
@cco解决了数字问题,但它仍然使linter不安。

你有很多解决问题的答案,如果你能标记其中一个为已解决,我相信他们会很感激。 - Labrys Knossos
5个回答

5

首先,因为您没有具体说明哪个lint错误,我假设是因为在sys.path.append之后有一个导入。

最干净的方法是使用相对或绝对导入。

使用绝对导入:

from parent_path.codeHelpers import completion_message

使用相对导入:

from ..codeHelpers import completion_message

针对原问题中列出的简单示例,这应该就是所需的全部内容。它简单、符合Python风格、可靠,并且修复了lint问题。

你可能会遇到一种情况,上述方法对你不起作用,仍需要操作sys.path。一个缺点是你的IDE很可能无法解析新路径中模块的导入,会导致自动代码补全不起作用并标记导入为错误,尽管代码仍能正常运行。

如果你发现仍需要使用sys.path并想要避免此类情况下的lint错误,可以创建一个新模块并在其中进行sys.path操作。然后确保在任何需要修改过的sys.path的模块之前导入你的新模块。

例如:

local_imports.py

"""Add a path to sys.path for imports."""

import os
import sys

# Get the current directory
current_path = os.path.dirname(__file__)

# Get the parent directory
parent_path = os.path.dirname(current_path)

# Add the parent directory to sys.path
sys.path.append(parent_path)

然后在目标文件中:
import local_imports  # now using modified sys.path
from codeHelpers import completion_message

这种方法的缺点是需要在每个child_folder中包含local_imports.py文件,如果文件夹结构发生变化,您需要修改每个local_imports文件。
当您需要在包中包含外部库(例如在libs文件夹中)而不需要用户自己安装这些库时,此模式非常有用。
如果您将此模式用于libs文件夹,则可能希望确保您包含的库优先于已安装的库。
为此,请更改:
sys.path.append(custom_path)

to

sys.path.insert(1, custom_path)

这将使你的自定义路径成为Python解释器查找模块的第二个地方(第一是本地目录'')。

3

你可以使用..从包的上一级模块中导入模块。在这个文件中(this_file.py):

from ..codeHelpers import completion_message

如果你想进一步向上,只需添加更多的点即可...
在此提醒您,from ..codeHelpers是相对导入,当在同一包中导入时应始终使用它们。 from codeHelpers是绝对导入,在Python 2中存在歧义(它应该从包中导入还是从您系统上安装的不幸命名为codeHelpers的模块中导入?),在Python 3中实际上被禁止作为从同一模块内导入的方式(即它们始终是绝对导入)。您可以阅读古老的PEP 328以了解其中困难的解释。

有点不太恰当的命名,code是学位名称,代表着computational design(计算机设计)。这是逻辑上的解释,但我可以理解为什么看起来像是我把我的帮助文件命名为programmingStuff - Ben
我尝试使用..codeHelpers,但是出现了ValueError: Attempted relative import in non-package error。这是否意味着其他地方存在问题,还是意味着我需要从另一个文件调用此文件而不是直接运行它? - Ben
那么我理解你是直接运行了this_file.py文件。通常情况下,我会建议不要混合使用模块和脚本代码。但如果你想将一个包也作为脚本来使用,可以使用__main__.py机制来实现。基本上,你需要将this_file.py重命名为child_folder中的__main__.py文件,并通过运行python -m parent_folder.child_folder来执行它,确保parent_folder和child_folder中都有__init__.py文件(如果使用python 2,则需要,在python 3中则不需要)。 - daphtdazz

3

使用绝对导入路径可能更容易,例如以下内容:

from parent_folder.code_helpers import completion_message

但是这将需要您确保设置了PYTHONPATH环境变量,以便它可以看到最高的根目录(在这种情况下是parent_folder)。例如:

PYTHONPATH=. python parent_directory/child_directory/this_file.py
# here the '.' current directory would contain parent_directory

请确保在子目录中也添加了一个__init__.py文件。


1
你可以使用os.path.dirname两次来消除关于最终目录名称长度的假设。
例如,使用os.path.dirname(os.path.dirname(__file__))代替os.path.dirname(__file__)[:-5]

1
无论如何,你都需要进行一些hack。如果你的主要目标是避免警告,可以采取以下措施:
  • 添加noqa注释
  • exec(open("../codeHelpers.py").read(), globals())

  • 你可以通过解释器选项-c传递文件名(不应该影响flakes8)


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