我正在运行Python 2.5。
这是我的文件夹树:
ptdraft/
nib.py
simulations/
life/
life.py
(每个文件夹中都有__init__.py
,为了可读性已省略)
我应该如何从life
模块内部导入nib
模块?我希望能在不修改sys.path的情况下完成。
注意:正在运行的主模块位于ptdraft
文件夹中。
我正在运行Python 2.5。
这是我的文件夹树:
ptdraft/
nib.py
simulations/
life/
life.py
(每个文件夹中都有__init__.py
,为了可读性已省略)
我应该如何从life
模块内部导入nib
模块?我希望能在不修改sys.path的情况下完成。
注意:正在运行的主模块位于ptdraft
文件夹中。
__init__.py
不是你必须做的唯一事情:https://dev59.com/HGgu5IYBdhLWcg3wJT9I#27876800 - Ben Farmer我也在关于从兄弟包中导入的问题上发布了类似的答案。您可以在这里看到。
sys.path
黑客的解决方案packaged_stuff
)setup.py
脚本,在其中使用setuptools.setup()。pip install -e <myproject_folder>
以可编辑状态安装软件包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
都可以工作。这只是一个最小的工作示例。
如果您熟悉虚拟环境,请先激活一个并跳过到下一步。 使用虚拟环境不是必须的,但它们将在长期上帮助您(当您有多个进行中的项目时..)。 最基本的步骤是(在根文件夹中运行)
python -m venv venv
. venv/bin/activate
(Linux)或./venv/Scripts/activate
(Win)deactivate
(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
安装您的顶级包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
来进行导入mainfolder
将会是 ptdraft
。这样做的好处是你不会遇到其他模块名称(来自 Python 标准库或第三方模块)的命名冲突。
def function_from_nib():
print('I am the return value from function_from_nib!')
from ptdraft.nib import function_from_nib
if __name__ == '__main__':
function_from_nib()
(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 更好(你可以把使用 PYTHONPATH 也归类为路径 hack),因为这意味着开发中的代码应该与其他用户表现一致。相比之下,当使用路径修改时,导入语句会以不同的方式解析。 - wim相对导入(如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
__file__
属性。建议使用inspect模块检索当前文件的文件名(和路径),而不是使用os.path.abspath(__file__)
。sys.path.insert(1, os.path.join(sys.path[0], '..'))
的翻译如下:
在系统路径中插入一个新的路径,该路径是当前文件所在目录的上一级目录。 - b0bzparentdir
之外的某个路径中,但在已经指定的sys.path
路径中有另一个名为“mymodule”的模块,则这很重要。将parentdir
插入sys.path
列表的第一个元素可以确保从parentdir
导入模块。 - Remisys.path.insert(1, os.path.realpath(os.path.pardir))
也可以起作用。 - SpeedCoder5看起来问题与模块所在的父目录等没有关系。
您需要将包含ptdraft
的目录添加到PYTHONPATH中。
您说import nib
可以工作,这可能意味着您将ptdraft
本身(而不是其父目录)添加到了PYTHONPATH中。
import sys
,然后再sys.path.append("..\<parent_folder>")
。 - BCJuansys.path.insert(0,'../sibling_dir')
。 - greenhousesys.path.append(os.path.dirname(os.path.abspath(__file__)))
。 - Flimm'..'
可以导航到包含所需模块的目录时,此方法才能起作用。 - user5359531sys.path.append(os.path.dirname(os.path.abspath(__file__)))
代替。这样做可以确保无论当前工作目录在哪里,都能正常工作。 - Flimm如果将您的模块文件夹添加到PYTHONPATH中没有起作用,您可以在程序中修改sys.path列表,Python解释器会在其中搜索要导入的模块,Python文档中说:
当导入名为spam的模块时,解释器首先搜索具有该名称的内置模块。 如果找不到,则在变量sys.path给出的目录列表中搜索名为spam.py的文件。 sys.path从以下位置初始化:
初始化后,Python程序可以修改sys.path。包含正在运行的脚本的目录被放置在搜索路径的开头,位于标准库路径之前。这意味着该目录中的脚本将被加载,而不是库目录中同名的模块。除非有意替换,否则这是一个错误。
- 包含输入脚本的目录(或当前目录)。
- PYTHONPATH(目录名称列表,与shell变量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
就必须进行编辑。还有一些解决方案可以找出文件所在的当前目录,并以此从父文件夹导入它。 - Edward这里有一个简单的答案,让你可以看到它是如何工作的,小巧且跨平台。它只使用内置模块(os
、sys
和 inspect
),因此应该可以在任何操作系统上运行,因为 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答案的例子如何在Python中获取当前执行文件的路径?,使用内置工具查找正在运行的代码的源(文件名)。
from inspect import getsourcefile from os.path import abspath
Next, wherever you want to find the source file from you just use:
abspath(getsourcefile(lambda:0))
我的代码将文件路径添加到sys.path
,即Python路径列表,因为这样可以让Python从该文件夹导入模块。
在代码中导入模块后,当添加的文件夹中有一个与程序后来导入的另一个模块同名的模块时,建议在新行上运行sys.path.pop(0)
。您需要删除导入之前添加的列表项,而不是其他路径。
如果您的程序不导入其他模块,则可以安全地不删除文件路径,因为在程序结束(或重新启动Python shell)后,对sys.path
所做的任何编辑都会消失。
我的答案不使用__file__
变量来获取正在运行的代码的文件路径/文件名,因为用户经常将其描述为不可靠。您不应在由其他人使用的程序中使用它来从父文件夹导入模块。
以下是一些它无法正常工作的示例(引用自this Stack Overflow问题):
py2exe
没有__file__
属性,但有一个解决方法- 当您使用
execute()
从IDLE运行时,没有__file__
属性- 在我使用
NameError:全局名称'__file__'未定义
的OS X 10.6中
sys.path.pop(0)
删除了新路径条目。然而,随后的导入仍然会到达该位置。例如,我有 app/config
、app/tools
和 app/submodule/config
。从 submodule
中,我插入 app/
来导入 tools
,然后删除 app/
并尝试导入 config
,但我得到的是 app/config
而不是 app/submodule/config
。 - user5359531tools
的一个导入也在从父目录导入config
。所以,当我稍后尝试在submodule
中执行sys.path.pop(0); import config
时,期望得到app/submodule/config
,实际上却得到了app/config
。显然,Python返回了同名模块的缓存版本,而不是实际检查sys.path
以匹配该名称的模块。在这种情况下,sys.path
被正确地更改,但由于已经加载了同名模块,Python没有进行检查。 - user5359531sys.modules
中查找模块名称*...*
sys.modules是可写的。删除一个键将使命名模块的缓存条目无效,导致Python在下次导入时重新搜索*...*
但要注意,如果您保留对模块对象的引用,使其缓存失效,然后重新导入,这两个对象将不同。相比之下,importlib.reload()
将重用和重新初始化模块内容*...* - Edwardos.path.realpath(getsourcefile(lambda:0) + "/../..")
。将获取当前源文件并附加两个父目录符号。我没有使用 os.path.sep
,因为 getsourcefile
在 Windows 上返回一个使用 /
的字符串。realpath
将从完整路径中弹出两个目录条目(在这种情况下是文件名和当前目录),从而得到父目录。 - Cobertospathlib库(包含在>= 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\nsys.path.insert(0,os.path.pardir)
同样的事情,但更简短 :)(注释中没有换行符) - Anti Veerannaos.path.pardir
,则无法获得父级的真实路径,只能获取从调用sys.path.insert()
的位置开始的相对路径。 - alvas__file__
变量,但这个变量可能存在不可靠性问题(并不总是完整的文件路径,在某些操作系统上无法工作等)。正如StackOverflow用户经常提到的那样,将其包含在内的答案会导致更多的问题,并且缺乏跨平台兼容性。想要了解更多信息,请参考https://dev59.com/QXRB5IYBdhLWcg3wH0SW#33532002. - Edward
__init__.py
文件了。S.Lott: 我不知道该如何检查... - Ram Rachumsys.path
或者PYTHONPATH
的答案,查看 np8 的优秀回答。是的,它很长。是的,看起来需要很多工作。但这是唯一一个真正正确且干净地解决问题的答案。 - Aran-Fey