我正在运行 Python 2.5。
这是我的文件夹树:
ptdraft/
nib.py
simulations/
life/
life.py
(我在每个文件夹中也有 __init__.py
,为了便于阅读,此处省略)
如何从 life
模块中导入 nib
模块?我希望无需修改 sys.path 就可以做到。
注意:正在运行的主模块位于 ptdraft
文件夹中。
__init__.py
。 S.Lott:我不知道如何检查......
sys.path
或 PYTHONPATH
答案并查看 np8's excellent answer。是的,读起来很长。是的,看起来工作量很大。但这是唯一能正确、干净地真正解决问题的答案。
您可以使用相对导入(python >= 2.5):
from ... import nib
(What’s New in Python 2.5) PEP 328: Absolute and Relative Imports
编辑:添加了另一个点“。”上两包
相对导入(如 from .. import mymodule
)仅在包中有效。要导入当前模块的父目录中的“mymodule”:
import os
import sys
import inspect
currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
parentdir = os.path.dirname(currentdir)
sys.path.insert(0, parentdir)
import mymodule
edit:并不总是给出 __file__
属性。我现在建议使用检查模块来检索当前文件的文件名(和路径)而不是使用 os.path.abspath(__file__)
sys.path.insert(1, os.path.join(sys.path[0], '..'))
parentdir
之外的其他地方可能很重要,但在 sys.path
中指定的路径之一中,还有另一个名为“mymodule”的模块。插入 parentdir
作为 sys.path
列表的第一个元素可确保将导入来自 parentdir
的模块。
sys.path.insert(1, os.path.realpath(os.path.pardir))
也可以。
关于从同级包导入的问题,我也发布了类似的答案。你可以看到它here。
没有 sys.path hacks 的解决方案
概括
将代码打包到一个文件夹中(例如 packaged_stuff)
在您使用 setuptools.setup() 的地方创建一个 setup.py 脚本。
使用 pip install -e
使用 from packaged_stuff.modulename import function_name 导入
设置
我假设与问题中的文件夹结构相同
.
└── ptdraft
├── __init__.py
├── nib.py
└── simulations
├── __init__.py
└── life
├── __init__.py
└── life.py
我将 .
称为根文件夹,就我而言,它位于 C:\tmp\test_imports
。
脚步
在根文件夹中添加一个 setup.py -- setup.py 的内容可以很简单
from setuptools import setup, find_packages
setup(name='myproject', version='1.0', packages=find_packages())
基本上“任何”setup.py
都可以。这只是一个最小的工作示例。
使用虚拟环境
如果您熟悉虚拟环境,请激活一个,然后跳到下一步。虚拟环境的使用不是绝对必要的,但从长远来看,它们确实会帮助你(当你有超过 1 个正在进行的项目时......)。最基本的步骤是(在根文件夹中运行)
创建虚拟环境 python -m venv venv
python -m venv venv
激活虚拟环境。 venv/bin/activate (Linux) 或 ./venv/Scripts/activate (Win)
. venv/bin/activate (Linux) 或 ./venv/Scripts/activate (Win)
停用虚拟环境停用 (Linux)
停用 (Linux)
要了解更多信息,只需谷歌搜索“python virtualenv 教程”或类似内容。除了创建、激活和停用之外,您可能永远不需要任何其他命令。
一旦你创建并激活了一个虚拟环境,你的控制台应该在括号中给出虚拟环境的名称
PS C:\tmp\test_imports> python -m venv venv
PS C:\tmp\test_imports> .\venv\Scripts\activate
(venv) PS C:\tmp\test_imports>
pip 以可编辑状态安装您的项目
使用 pip
安装您的顶级软件包 myproject
。诀窍是在安装时使用 -e
标志。这样它以可编辑状态安装,对 .py 文件所做的所有编辑都将自动包含在安装的包中。
在根目录下,运行
pip install -e .
(注意点,它代表“当前目录”)
您还可以看到它是使用 pip freeze
安装的
(venv) PS C:\tmp\test_imports> pip install -e .
Obtaining file:///C:/tmp/test_imports
Installing collected packages: myproject
Running setup.py develop for myproject
Successfully installed myproject
(venv) PS C:\tmp\test_imports> pip freeze
myproject==1.0
通过在每次导入前添加主文件夹来导入
在此示例中,mainfolder
将是 ptdraft
。这样做的好处是您不会遇到与其他模块名称(来自 python 标准库或 3rd 方模块)的名称冲突。
示例用法
笔尖
def function_from_nib():
print('I am the return value from function_from_nib!')
生活.py
from ptdraft.nib import function_from_nib
if __name__ == '__main__':
function_from_nib()
运行生活.py
(venv) PS C:\tmp\test_imports> python .\ptdraft\simulations\life\life.py
I am the return value from function_from_nib!
sys.path
上,并且代码通常由最终用户安装。这比 sys.path
hack(并且您可以在该类别的路径 hack 中包括使用 PYTHONPATH)更好,因为这意味着开发中的代码对您的行为方式应该与对其他用户的行为方式相同。相反,当使用路径修改时,导入语句将以不同的方式解析。
似乎问题与位于父目录或类似目录中的模块无关。
您需要将包含 ptdraft
的目录添加到 PYTHONPATH
您说 import nib
与您合作,这可能意味着您将 ptdraft
本身(而不是其父级)添加到 PYTHONPATH。
import sys
,然后是 sys.path.append("..\<parent_folder>")
您可以在 sys.path 中列出的“模块搜索路径”中使用取决于操作系统的路径。因此,您可以轻松添加父目录,如下所示
import sys
sys.path.insert(0,'..')
如果要添加父父目录,
sys.path.insert(0,'../..')
这适用于 python 2 和 3。
sys.path.insert(0,'../sibling_dir')
对python 2不太了解,在python 3中可以添加父文件夹如下:
import sys
sys.path.append('..')
...然后可以从中导入模块
'..'
指向包含相关模块的目录时才有效。
如果将模块文件夹添加到 PYTHONPATH 不起作用,您可以在 Python 解释器搜索要导入的模块的程序中修改 sys.path 列表,python documentation 表示:
当导入名为 spam 的模块时,解释器首先搜索具有该名称的内置模块。如果没有找到,它会在变量 sys.path 给出的目录列表中搜索名为 spam.py 的文件。 sys.path 从以下位置初始化:包含输入脚本的目录(或当前目录)。 PYTHONPATH(目录名称列表,与 shell 变量 PATH 具有相同的语法)。安装相关的默认值。初始化后,Python 程序可以修改 sys.path。包含正在运行的脚本的目录位于搜索路径的开头,位于标准库路径之前。这意味着将加载该目录中的脚本,而不是库目录中的同名模块。除非打算更换,否则这是一个错误。
知道了这一点,您可以在程序中执行以下操作:
import sys
# Add the ptdraft folder path to the sys.path list
sys.path.append('/path/to/ptdraft/')
# Now you can import your module
from ptdraft import nib
# Or just
import ptdraft
/path/to/ptdraft
。有一些解决方案可以计算出文件的当前目录并以这种方式从父文件夹中导入它。
这是一个简单的答案,因此您可以了解它是如何工作的,小型且跨平台。
它只使用内置模块(os
、sys
和 inspect
),因此应该可以在
任何操作系统 (OS),因为 Python 就是为此而设计的。
更短的答案代码 - 更少的行和变量
from inspect import getsourcefile
import os.path as path, sys
current_dir = path.dirname(path.abspath(getsourcefile(lambda:0)))
sys.path.insert(0, current_dir[:current_dir.rfind(path.sep)])
import my_module # Replace "my_module" here with the module name.
sys.path.pop(0)
如果行数少于此值,请将第二行替换为 import os.path as path, sys, inspect
,
在 getsourcefile
(第 3 行)的开头添加 inspect.
并删除第一行。
- 但是这会导入所有模块所以可能需要更多的时间、内存和资源。
我的答案的代码(更长的版本)
from inspect import getsourcefile
import os.path
import sys
current_path = os.path.abspath(getsourcefile(lambda:0))
current_dir = os.path.dirname(current_path)
parent_dir = current_dir[:current_dir.rfind(os.path.sep)]
sys.path.insert(0, parent_dir)
import my_module # Replace "my_module" here with the module name.
它使用 Stack Overflow 答案中的示例How do I get the path of the current
executed file in Python? 来使用内置工具查找运行代码的源(文件名)。
from inspect import getsourcefile from os.path import abspath 接下来,无论您想从哪里找到源文件,只需使用: abspath(getsourcefile(lambda:0))
我的代码将文件路径添加到 sys.path
,即 python 路径列表
,因为这允许 Python 从该文件夹导入模块。
在代码中导入模块后,如果添加的文件夹具有与稍后在程序中导入的另一个模块同名的模块
,最好在新行上运行 sys.path.pop(0)
。您需要删除导入之前添加的列表项,而不是其他路径。
如果您的程序没有导入其他模块,则不删除文件路径是安全的,因为
在程序结束(或重新启动 Python shell),对 sys.path
所做的任何编辑都会消失。
关于文件名变量的注释
我的回答没有使用 __file__
变量来获取运行
代码的文件路径/文件名,因为这里的用户经常将其描述为不可靠。您不应该在其他人使用的程序中使用它
从父文件夹导入模块。
一些不起作用的例子(引自 this Stack Overflow 问题):
• 它can't be在某些平台上发现 • 它有时isn't the full file path
py2exe 没有 __file__ 属性,但是有一种解决方法当您使用 execute() 从 IDLE 运行时,没有 __file__ 属性 OS X 10.6 出现 NameError: global name '__file__' is not defined
sys.path.pop(0)
删除了新路径条目。然而,随后的进口仍然去了那个位置。例如,我有 app/config
、app/tools
和 app/submodule/config
。从 submodule
,我插入 app/
以导入 tools
,然后删除 app/
并尝试导入 config
,但我得到的是 app/config
而不是 app/submodule/config
。
tools
的其中一个导入也是从父目录导入 config
。因此,当我后来尝试在 submodule
中执行 sys.path.pop(0); import config
并期望得到 app/submodule/config
时,我实际上得到了 app/config
。显然,Python 返回同名模块的缓存版本,而不是实际检查 sys.path
中是否有匹配该名称的模块。 sys.path
在这种情况下被正确更改,Python 只是没有检查它,因为已经加载了同名模块。
sys.modules
中查找模块名称 ... sys.modules 是可写的。删除键将使命名模块的缓存条目无效,导致 Python 在下一次导入时重新搜索。 ... 但请注意,如果您保留对模块对象的引用,使其缓存无效然后重新导入,这两个对象将是不同的。相比之下,importlib.reload()
将重用并重新初始化模块内容 ...
os.path.realpath(getsourcefile(lambda:0) + "/../..")
。将获取附加两个父目录符号的当前源文件。我没有使用 os.path.sep
,因为 getsourcefile
即使在 Windows 上也使用 /
返回字符串。 realpath
将负责从提供父目录的完整路径(在本例中为文件名,然后是当前目录)中弹出两个目录条目。
pathlib 库(包含在 >= Python 3.4 中)使得将父目录的路径附加到 PYTHONPATH 变得非常简洁和直观:
import sys
from pathlib import Path
sys.path.append(str(Path('.').absolute().parent))
这是更通用的解决方案,将父目录包含在 sys.path 中(对我有用):
import os.path, sys
sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))
import os, sys\n sys.path.insert(0,os.path.pardir)
相同,但排序器 :)(评论中没有换行符)
os.path.pardir
,您将不会获得父级的真实路径,只是您调用 sys.path.insert()
的相对路径
__file__
变量(并不总是完整的文件路径,不适用于每个操作系统等)。将答案更改为不包括它将导致更少的问题并且更具交叉兼容性。有关详细信息,请参阅 stackoverflow.com/a/33532002/3787376。
在 Jupyter Notebook 中(使用 Jupyter LAB 或 Jupyter Notebook 打开)
只要您在 Jupyter Notebook 中工作,这个简短的解决方案可能会很有用:
%cd ..
import nib
即使没有 __init__.py
文件,它也可以工作。
我在 Linux 和 Windows 7 上使用 Anaconda3 对其进行了测试。
%cd -
是否有意义,因此笔记本的当前目录保持不变。
我发现以下方法适用于从脚本的父目录导入包。在示例中,我想从 app.db
包中导入 env.py
中的函数。
.
└── my_application
└── alembic
└── env.py
└── app
├── __init__.py
└── db
import os
import sys
currentdir = os.path.dirname(os.path.realpath(__file__))
parentdir = os.path.dirname(currentdir)
sys.path.append(parentdir)
sys.path.append(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
sys.path
通常是个坏主意。您需要某种方式(eg、PYTHONPATH
)让other 代码可以访问您的库(否则它不是真正的库);一直使用它!
上面提到的解决方案也很好。这个问题的另一个解决方案是
如果您想从顶级目录导入任何内容。然后,
from ...module_name import *
此外,如果您想从父目录导入任何模块。然后,
from ..module_name import *
此外,如果您想从父目录导入任何模块。然后,
from ...module_name.another_module import *
这样,您可以根据需要导入任何特定方法。
sys.path.append
hacks 不同,我无法像这样导入我的库。这是我在开始 google 之前第一次尝试做的事情 :) 好的,就是这个 ValueError: attempted relative import beyond top-level package
from ... import name
并不意味着“从顶级导入名称”,而是“从父级的父级导入名称”。我创建了一个包含九个目录的嵌套目录结构 a1/.../a9
,其中每个目录都包含一个 __init__.py
,并使用 from . import next_dir
导入下一个目录。最后一个目录有一个带有 from ....... import b
的文件,然后导入了 a1/a2/a3/b
。 (使用 Python 3.8.2 测试)
import sys sys.path.append('../')
对我来说,访问父目录的最短且我最喜欢的 oneliner 是:
sys.path.append(os.path.dirname(os.getcwd()))
或者:
sys.path.insert(1, os.path.dirname(os.getcwd()))
os.getcwd() 返回当前工作目录的名称,os.path.dirname(directory_name) 返回传递的目录名称。
实际上,在我看来,Python 项目架构应该按照子目录中的任何模块都不会使用父目录中的任何模块的方式来完成。如果发生这样的事情,值得重新考虑项目树。
另一种方法是将父目录添加到 PYTHONPATH 系统环境变量。
虽然原作者可能不再寻找解决方案,但为了完整性,有一个简单的解决方案。将 life.py 作为一个模块运行,如下所示:
cd ptdraft
python -m simulations.life.life
这样,您可以从 nib.py 导入任何内容,因为 ptdraft 目录位于路径中。
两行最简单的解决方案
import os, sys
sys.path.insert(0, os.getcwd())
如果 parent 是您的工作目录,并且您想从子脚本中调用另一个子模块。
您可以在任何脚本中从父目录导入所有子模块并将其执行为
python child_module1/child_script.py
与过去的答案相同的风格 - 但行数更少:P
import os,sys
parentdir = os.path.dirname(__file__)
sys.path.insert(0,parentdir)
文件返回您正在工作的位置
在 Linux 系统中,您可以创建从“life”文件夹到 nib.py 文件的软链接。然后,您可以像这样简单地导入它:
import nib
我有一个专门针对 git-repositories 的解决方案。
首先,我使用了 sys.path.append('..')
和类似的解决方案。如果您正在导入本身使用 sys.path.append('..')
导入文件的文件,这会导致特别问题。
然后我决定总是附加 git 存储库的根目录。在一行中,它看起来像这样:
sys.path.append(git.Repo('.', search_parent_directories=True).working_tree_dir)
或者像这样的更多细节:
import os
import sys
import git
def get_main_git_root(path):
main_repo_root_dir = git.Repo(path, search_parent_directories=True).working_tree_dir
return main_repo_root_dir
main_repo_root_dir = get_main_git_root('.')
sys.path.append(main_repo_root_dir)
对于原始问题:根据存储库的根目录,导入将是
import ptdraft.nib
或者
import nib
我们的文件夹结构:
/myproject
project_using_ptdraft/
main.py
ptdraft/
__init__.py
nib.py
simulations/
__init__.py
life/
__init__.py
life.py
我理解这一点的方式是有一个以包为中心的视图。包根是 ptdraft
,因为它是包含 __init__.py
的最顶层
包中的所有文件都可以使用绝对路径(相对于包根目录)进行导入,例如在 life.py
中,我们只需:
import ptdraft.nib
但是,要为包开发/测试目的运行 life.py
,而不是 python life.py
,我们需要使用:
cd /myproject
python -m ptdraft.simulations.life.life
请注意,此时我们根本不需要摆弄任何路径。
更令人困惑的是,当我们完成 ptdraft
包时,我们想在驱动程序脚本中使用它,该脚本必须位于 ptdraft
包文件夹(又名 project_using_ptdraft/main.py
)之外,我们需要修改路径:
import sys
sys.path.append("/myproject") # folder that contains ptdraft
import ptdraft
import ptdraft.simulations
并使用 python main.py
运行脚本没有问题。
有用的网址:
https://tenthousandmeters.com/blog/python-behind-the-scenes-11-how-the-python-import-system-works/(查看如何使用 __init__.py)
https://chrisyeh96.github.io/2017/08/08/definitive-guide-python-imports.html#running-package-initialization-code
https://stackoverflow.com/a/50392363/2202107
https://stackoverflow.com/a/27876800/2202107
与图书馆合作。创建一个名为 nib 的库,使用 setup.py 安装它,让它驻留在站点包中,您的问题就解决了。你不必把你做的所有东西都塞进一个包里。把它分解成碎片。
site-packages
。
我遇到了一个问题,我必须导入一个 Flask 应用程序,该应用程序的导入还需要在单独的文件夹中导入文件。这部分使用了 Remi's answer,但假设我们有一个如下所示的存储库:
.
└── service
└── misc
└── categories.csv
└── test
└── app_test.py
app.py
pipeline.py
然后在从 app.py
文件导入应用程序对象之前,我们将目录更改为上一级,因此当我们导入应用程序(导入 pipeline.py
)时,我们还可以读取其他文件,如 csv 文件。
import os,sys,inspect
currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
parentdir = os.path.dirname(currentdir)
sys.path.insert(0,parentdir)
os.chdir('../')
from app import app
导入 Flask 应用程序后,您可以使用 os.chdir('./test')
,这样您的工作目录就不会更改。
在我看来,您实际上并不需要导入父模块。假设在 nib.py 中有 func1() 和 data1,需要在 life.py 中使用
笔尖
import simulations.life.life as life
def func1():
pass
data1 = {}
life.share(func1, data1)
生活.py
func1 = data1 = None
def share(*args):
global func1, data1
func1, data1 = args
现在您可以访问 life.py 中的 func1 和数据了。当然,在尝试使用它们之前,你必须小心在 life.py 中填充它们,
我制作了这个库来做到这一点。
https://github.com/fx-kirin/add_parent_path
# Just add parent path
add_parent_path(1)
# Append to syspath and delete when the exist of with statement.
with add_parent_path(1):
# Import modules in the parent path
pass
这是对我有用的最简单的解决方案:
from ptdraft import nib
虽然违反了所有规则,但我还是要提一下这种可能性:
您可以先将文件从父目录复制到子目录。接下来导入它并随后删除复制的文件:
例如在 life.py
中:
import os
import shutil
shutil.copy('../nib.py', '.')
import nib
os.remove('nib.py')
# now you can use it just fine:
nib.foo()
当然,当 nibs 尝试使用相对导入/路径导入/读取其他文件时,可能会出现一些问题。
sys.path
(如其他答案中所建议的那样)。复制/删除文件会引入对文件位置的非常紧密的依赖,这在将来更改项目时可能会被忽视(从而使脚本的健壮性降低)。它还可以防止文件被缓存,当然也会为所需的 IO 产生轻微的开销。
这对我来说可以从更高的文件夹中导入东西。
import os
os.chdir('..')
ValueError: Attempted relative import in non-package
__init__.py
不是您唯一需要做的事情:stackoverflow.com/questions/11536764/…__init__.py
文件。