564 python import relative-path python-2.7
我来过这里:
还有很多我没有复制的网址,有些在SO上,有些在其他网站上,当我认为我有快速解决方案的时候.
永远反复出现的问题是:使用Windows 7,32位Python 2.7.3,如何解决这个"非包装中尝试相对导入"的消息?我在pep-0328上构建了一个包的精确副本:
package/
__init__.py
subpackage1/
__init__.py
moduleX.py
moduleY.py
subpackage2/
__init__.py
moduleZ.py
moduleA.py
Run Code Online (Sandbox Code Playgroud)
我确实在适当的模块中创建了名为spam和eggs的函数.当然,它没有用.答案显然在我列出的第4个网址中,但这对我来说都是校友.我访问过的其中一个网址上有此回复:
相对导入使用模块的name属性来确定模块在包层次结构中的位置.如果模块的名称不包含任何包信息(例如,它设置为'main'),则解析相对导入,就像模块是顶级模块一样,无论模块实际位于文件系统的哪个位置.
上面的反应看起来很有希望,但它对我来说都是象形文字.所以我的问题是,如何让Python不回归"尝试非包装中的相对导入"?有一个答案涉及-m,据说.
有人可以告诉我为什么Python会给出错误信息,非包装意味着什么!,为什么以及如何定义"包裹",并且准确的答案足以让幼儿园儿童理解.
编辑:导入是从控制台完成的.
Bre*_*arn 848
脚本与模块
这是一个解释.简短版本是直接运行Python文件和从其他地方导入该文件之间存在很大差异. 只知道文件所在的目录并不能确定Python认为它所在的包. 这另外还取决于如何将文件加载到Python中(通过运行或导入).
加载Python文件有两种方法:作为顶级脚本或作为模块.如果直接执行文件,则将文件作为顶级脚本加载,例如通过python myfile.py在命令行上键入.如果您这样做python -m myfile,它将作为模块加载,或者import在其他文件中遇到语句时加载它.一次只能有一个顶级脚本; 顶级脚本是您运行的Python文件.
命名
加载文件时,会为其指定一个名称(存储在其__name__属性中).如果它作为顶级脚本加载,则其名称为__main__.如果它作为模块加载,则其名称是文件名,前面是作为其一部分的任何包/子包的名称,以点分隔.
例如,在您的示例中:
package/
__init__.py
subpackage1/
__init__.py
moduleX.py
moduleA.py
Run Code Online (Sandbox Code Playgroud)
如果你导入moduleX(注意:导入,不直接执行),它的名字就是package.subpackage1.moduleX.如果您导入moduleA,其名称将是package.moduleA.但是,如果直接从命令行运行 moduleX,则其名称将改为__main__,如果直接从命令行运行moduleA,则其名称将为__main__.当模块作为顶级脚本运行时,它会丢失其正常名称,而是其名称__main__.
不通过其包含的包访问模块
还有一个问题:模块的名称取决于它是从"目录"中"直接"导入还是通过包导入.如果您在目录中运行Python,并尝试在同一目录(或其子目录)中导入文件,这只会有所不同.例如,如果你在目录中启动Python解释器package/subpackage1然后执行import moduleX,那么moduleXwill 的名称就是moduleX,而不是package.subpackage1.moduleX.这是因为Python在启动时将当前目录添加到其搜索路径中; 如果它在当前目录中找到要导入的模块,它将不知道该目录是包的一部分,并且包信息不会成为模块名称的一部分.
一个特例是如果您以交互方式运行解释器(例如,只需键入python并开始即时输入Python代码).在这种情况下,该交互式会话的名称是__main__.
现在,这是您的错误消息的关键所在:如果模块的名称没有点,则不认为它是包的一部分.文件在磁盘上的实际位置无关紧要.重要的是它的名称,它的名称取决于你如何加载它.
现在看看你在问题中包含的引用:
相对导入使用模块的name属性来确定模块在包层次结构中的位置.如果模块的名称不包含任何包信息(例如,它设置为'main'),则解析相对导入,就像模块是顶级模块一样,无论模块实际位于文件系统的哪个位置.
相对进口......
相对导入使用模块的名称来确定它在包中的位置.当您使用相对导入时from .. import foo,这些点表示在包层次结构中增加一些级别.例如,如果您当前模块的名称是package.subpackage1.moduleX,则..moduleA意味着package.moduleA.要from .. import使其工作,模块的名称必须至少与import语句中的点一样多.
......只是一个包中的亲戚
但是,如果您的模块名称是__main__,则不认为它在包中.它的名字没有点,因此你不能from .. import在其中使用语句.如果您尝试这样做,您将收到"非包装中的相对导入"错误.
脚本无法导入相对
你可能做的是你试图从命令行运行moduleX等.当您执行此操作时,其名称设置为__main__,这意味着其中的相对导入将失败,因为其名称不会显示它在包中.请注意,如果您从模块所在的同一目录运行Python,然后尝试导入该模块,也会发生这种情况,因为如上所述,Python会在"太早"找到当前目录中的模块,而不会意识到它是包的一部分.
还要记住,当您运行交互式解释器时,该交互式会话的"名称"始终是__main__.因此,您无法直接从交互式会话中执行相对导入.相对导入仅用于模块文件.
两种解决方案
如果你真的想moduleX直接运行,但你仍然希望它被视为包的一部分,你可以这样做python -m package.subpackage1.moduleX.该-m告诉Python来加载它作为一个模块,而不是顶级的脚本.
或许你并不真的想运行 moduleX,你只是想运行其他一些脚本,说myfile.py,是使用内部功能moduleX.如果是这样的话,把myfile.py 其他地方 - 没有内部package目录-并运行它.如果在myfile.py你内心做的事情from package.moduleA import spam,它会工作正常.
笔记
对于这些解决方案中的任何一个,package必须可以从Python模块搜索路径(sys.path)访问包目录(在您的示例中).如果不是,您根本无法可靠地使用包装中的任何东西.
从Python 2.6开始,用于包解析目的的模块"名称"不仅取决于其__name__属性,还取决于__package__属性.这就是为什么我避免使用显式符号__name__来引用模块的"名称".因为Python 2.6模块的"名"是有效的__package__ + '.' + __name__,或者只是__name__如果__package__是None).
Smi*_*nth 52
外语的答案太多了。所以,我会尽量长话短说。
如果您编写from . import module,与您的想法相反,module将不会从当前目录导入,而是从包的顶层导入!如果您将.py文件作为脚本运行,它根本不知道顶层在哪里,因此拒绝工作。
如果你像这样py -m package.module从上面的目录启动它package,那么Python就知道顶层在哪里。这与 Java 非常相似:java -cp bin_directory package.class
小智 29
这在python中确实是一个问题.混淆的起因是人们错误地将相对导入作为路径相对而不是.
例如,当你在faa.py中写道时:
from .. import foo
Run Code Online (Sandbox Code Playgroud)
这具有只有一个意思faa.py被识别并加载由蟒,在执行期间,作为一个包的一部分.在这种情况下,该模块的名称 为faa.py将是例如some_packagename.faa.如果文件是因为它在当前目录中而加载的,那么当运行python时,它的名称将不会引用任何包,最终相对导入将失败.
在当前目录中引用模块的简单解决方案是使用:
if __package__ is None or __package__ == '':
# uses current directory visibility
import foo
else:
# uses current package visibility
from . import foo
Run Code Online (Sandbox Code Playgroud)
Fed*_*ico 16
这是我不推荐的一种解决方案,但在某些根本未生成模块的情况下可能有用:
import os
import sys
parent_dir_name = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
sys.path.append(parent_dir_name + "/your_dir")
import your_script
your_script.a_function()
Run Code Online (Sandbox Code Playgroud)
因此,在与许多其他人一起讨论这个问题之后,我发现了Dorian B在本文中发布的一条说明,该说明解决了我在开发用于 Web 服务的模块和类时遇到的特定问题,但我也想成为能够在我编码时使用 PyCharm 中的调试器工具测试它们。要在自包含类中运行测试,我将在类文件的末尾包含以下内容:
if __name__ == '__main__':
# run test code here...
Run Code Online (Sandbox Code Playgroud)
但是如果我想在同一文件夹中导入其他类或模块,那么我必须将所有导入语句从相对符号更改为本地引用(即删除点 (.))但是在阅读了 Dorian 的建议之后,我尝试了他的 '单线',它奏效了!我现在可以在 PyCharm 中进行测试,并在我在另一个被测类中使用该类或在我的 Web 服务中使用它时保留我的测试代码!
# import any site-lib modules first, then...
import sys
parent_module = sys.modules['.'.join(__name__.split('.')[:-1]) or '__main__']
if __name__ == '__main__' or parent_module.__name__ == '__main__':
from codex import Codex # these are in same folder as module under test!
from dblogger import DbLogger
else:
from .codex import Codex
from .dblogger import DbLogger
Run Code Online (Sandbox Code Playgroud)
if 语句检查我们是否将此模块作为main运行,或者它是否正在另一个被测试为main 的模块中使用。也许这很明显,但我在此提供此说明,以防其他对上述相关导入问题感到沮丧的人可以使用它。
这是一个通用的配方,经过修改以适合作为示例,我现在使用它来处理以程序包形式编写的Python库,其中包含相互依赖的文件,我希望能够逐个测试其中的某些部分。让我们称之为它lib.foo,说它需要lib.fileA对函数f1和f2和lib.fileB类的访问Class3。
我打了几个print电话,以帮助说明这是如何工作的。实际上,您可能希望将其删除(也许还删除该from __future__ import print_function行)。
这个特定的例子太简单了,无法显示何时确实需要在中插入条目sys.path。(见拉尔斯的回答为我们的情况下,就需要它,当我们有包目录中的两个或两个以上的水平,然后我们使用os.path.dirname(os.path.dirname(__file__))-但它并没有真正伤害在这里无论是。)它也足够安全要做到这一点,而不if _i in sys.path测试。但是,如果每个导入文件插入相同的路径-例如,如果两个fileA并fileB希望导入实用程序从包中,这个杂波了sys.path具有相同路径很多次,所以很高兴有if _i not in sys.path在样板。
from __future__ import print_function # only when showing how this works
if __package__:
print('Package named {!r}; __name__ is {!r}'.format(__package__, __name__))
from .fileA import f1, f2
from .fileB import Class3
else:
print('Not a package; __name__ is {!r}'.format(__name__))
# these next steps should be used only with care and if needed
# (remove the sys.path manipulation for simple cases!)
import os, sys
_i = os.path.dirname(os.path.abspath(__file__))
if _i not in sys.path:
print('inserting {!r} into sys.path'.format(_i))
sys.path.insert(0, _i)
else:
print('{!r} is already in sys.path'.format(_i))
del _i # clean up global name space
from fileA import f1, f2
from fileB import Class3
... all the code as usual ...
if __name__ == '__main__':
import doctest, sys
ret = doctest.testmod()
sys.exit(0 if ret.failed == 0 else 1)
Run Code Online (Sandbox Code Playgroud)
这里的想法是这样的(请注意,在python2.7和python 3.x中这些功能都相同):
如果从普通代码导入为常规软件包import lib或from lib import foo作为常规软件包运行,__package则is lib和__name__is lib.foo。我们采用第一个代码路径,从.fileA等导入。
如果运行为python lib/foo.py,__package__则将为None且__name__将为__main__。
我们采用第二条代码路径。该lib目录已经存在,sys.path因此无需添加它。我们从fileA等导入
如果在lib目录中以身份运行python foo.py,则其行为与情况2相同。
如果在libas目录中运行python -m foo,其行为类似于情况2和3。但是,lib目录的路径不在in中sys.path,因此我们在导入之前将其添加。如果我们先运行Python然后运行,则同样适用import foo。
(由于. 是在sys.path,我们并不真正需要添加此路径的绝对的版本。这是一个更深层次的包嵌套结构,我们想要做的from ..otherlib.fileC import ...,有差别。如果你不这样做,就可以在sys.path完全省略所有操作。)
仍然有一个怪癖。如果从外部运行整个过程:
$ python2 lib.foo
Run Code Online (Sandbox Code Playgroud)
要么:
$ python3 lib.foo
Run Code Online (Sandbox Code Playgroud)
行为取决于的内容lib/__init__.py。如果存在并且为空,则一切正常:
Package named 'lib'; __name__ is '__main__'
Run Code Online (Sandbox Code Playgroud)
但是,如果lib/__init__.py 本身导入,routine以便可以routine.name直接将导出为lib.name,则会得到:
$ python2 lib.foo
Package named 'lib'; __name__ is 'lib.foo'
Package named 'lib'; __name__ is '__main__'
Run Code Online (Sandbox Code Playgroud)
也就是说,该模块被导入两次,一次是通过包导入的,然后是再次导入的,__main__以便它运行您的main代码。Python 3.6及更高版本对此发出警告:
$ python3 lib.routine
Package named 'lib'; __name__ is 'lib.foo'
[...]/runpy.py:125: RuntimeWarning: 'lib.foo' found in sys.modules
after import of package 'lib', but prior to execution of 'lib.foo';
this may result in unpredictable behaviour
warn(RuntimeWarning(msg))
Package named 'lib'; __name__ is '__main__'
Run Code Online (Sandbox Code Playgroud)
该警告是新的,但警告说,有关的行为是不能。这就是所谓的双重导入陷阱的一部分。(有关其他详细信息,请参见问题27487。)尼克·科格兰(Nick Coghlan)说:
下一个陷阱存在于所有当前的Python版本(包括3.3)中,并且可以在以下常规准则中进行总结:“切勿将包目录或包内的任何目录直接添加到Python路径中”。
请注意,虽然此处违反了该规则,但仅在不将要加载的文件作为软件包的一部分加载时才这样做,并且我们所做的修改专门用于允许我们访问该软件包中的其他文件。(而且,正如我所指出的,我们可能根本不应该对单层程序包执行此操作。)如果我们想变得更加干净,可以将其重写为例如:
import os, sys
_i = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if _i not in sys.path:
sys.path.insert(0, _i)
else:
_i = None
from sub.fileA import f1, f2
from sub.fileB import Class3
if _i:
sys.path.remove(_i)
del _i
Run Code Online (Sandbox Code Playgroud)
也就是说,我们进行了sys.path足够长的修改以实现导入,然后将其恢复原样(_i如果且仅当我们添加的一个副本时,删除一个副本_i)。
@BrenBarn 的回答说明了一切,但如果你像我一样,可能需要一段时间才能理解。这是我的案例以及@BrenBarn 的答案如何适用于它,也许会对您有所帮助。
案子
package/
__init__.py
subpackage1/
__init__.py
moduleX.py
moduleA.py
Run Code Online (Sandbox Code Playgroud)
使用我们熟悉的示例,并添加 moduleX.py 与 ..moduleA 的相对导入。鉴于我尝试在导入 moduleX 的 subpackage1 目录中编写测试脚本,但随后出现了 OP 描述的可怕错误。
解决方案
将测试脚本移至与包相同的级别并导入 package.subpackage1.moduleX
解释
正如所解释的,相对导入是相对于当前名称进行的。当我的测试脚本从同一目录导入 moduleX 时,moduleX 内的模块名称是 moduleX。当遇到相对导入时,解释器无法备份包层次结构,因为它已经位于顶部
当我从上面导入 moduleX 时,moduleX 内部的名称是 package.subpackage1.moduleX 并且可以找到相对导入
小智 5
我遇到了类似的问题,我不想更改 Python 模块搜索路径,并且需要从脚本相对加载模块(尽管BrenBarn很好地解释了“脚本无法导入所有内容”)。
所以我使用了以下技巧。不幸的是,它依赖imp于自 3.4 版本以来已弃用的模块,该模块已被放弃以支持importlib. (这importlib也可以用吗?我不知道。)不过,这个黑客目前仍然有效。
从驻留在文件夹中的脚本访问moduleXin成员的示例:subpackage1subpackage2
#!/usr/bin/env python3
import inspect
import imp
import os
def get_script_dir(follow_symlinks=True):
"""
Return directory of code defining this very function.
Should work from a module as well as from a script.
"""
script_path = inspect.getabsfile(get_script_dir)
if follow_symlinks:
script_path = os.path.realpath(script_path)
return os.path.dirname(script_path)
# loading the module (hack, relying on deprecated imp-module)
PARENT_PATH = os.path.dirname(get_script_dir())
(x_file, x_path, x_desc) = imp.find_module('moduleX', [PARENT_PATH+'/'+'subpackage1'])
module_x = imp.load_module('subpackage1.moduleX', x_file, x_path, x_desc)
# importing a function and a value
function = module_x.my_function
VALUE = module_x.MY_CONST
Run Code Online (Sandbox Code Playgroud)
一种更干净的方法似乎是修改Federico 提到的用于加载模块的 sys.path 。
#!/usr/bin/env python3
if __name__ == '__main__' and __package__ is None:
from os import sys, path
# __file__ should be defined in this case
PARENT_DIR = path.dirname(path.dirname(path.abspath(__file__)))
sys.path.append(PARENT_DIR)
from subpackage1.moduleX import *
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
166003 次 |
| 最近记录: |