问题:相对进口量为十亿次
我来过这里:
- http://www.python.org/dev/peps/pep-0328/
- http://docs.python.org/2/tutorial/modules.html#packages
- Python软件包:相对导入
- python相对导入示例代码不起作用
- 相对python导入的最终答案
- Python中的相对导入
- Python:禁用相对导入
以及很多我没有复制的URL,有些在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
导入是从控制台完成的。
我确实在相应的模块中创建了名为垃圾邮件和鸡蛋的函数。自然,它不起作用。答案显然是在我列出的第4个网址中,但对我来说都是校友。我访问的其中一个URL上有此响应:
相对导入使用模块的名称属性来确定该模块在包层次结构中的位置。如果模块的名称不包含任何包信息(例如,将其设置为“ main”),则相对导入的解析就好像该模块是顶级模块一样,无论该模块实际位于文件系统上的哪个位置。
上面的回答看起来很有希望,但对我来说,全都是象形文字。所以我的问题是,如何使Python不返回“未包装的相对导入尝试”?可能有一个涉及-m的答案。
有人可以告诉我为什么Python会给出该错误消息,“非包装”的含义,为什么以及如何定义“包装”以及准确的答案,这些措辞足以使幼儿园的学生理解。
回答 0
脚本与模块
这是一个解释。简短的版本是直接运行Python文件与从其他位置导入该文件之间存在很大差异。 仅知道文件位于哪个目录并不能确定Python认为位于哪个软件包。 此外,这还取决于您如何通过运行或导入将文件加载到Python中。
加载Python文件的方式有两种:作为顶级脚本或作为模块。如果直接执行文件(例如,python myfile.py
在命令行上键入),则将文件作为顶级脚本加载。如果您这样做python -m myfile
,则将其作为模块加载,或者import
在其他文件中遇到语句时将其加载。一次只能有一个顶级脚本。顶层脚本是您为了开始而运行的Python文件。
命名
加载文件时,将为其指定一个名称(存储在其__name__
属性中)。如果已将其作为顶级脚本加载,则其名称为__main__
。如果将其作为模块加载,则其名称为文件名,其后是其所属的所有软件包/子软件包的名称,并用点号分隔。
例如,在您的示例中:
package/
__init__.py
subpackage1/
__init__.py
moduleX.py
moduleA.py
如果您导入moduleX
(请注意:imported,不直接执行),则其名称为package.subpackage1.moduleX
。如果导入moduleA
,则名称为package.moduleA
。但是,如果直接从命令行运行 moduleX
,则名称为__main__
,如果直接从命令行运行moduleA
,则名称为__main__
。当模块作为顶级脚本运行时,它将失去其常规名称,而其名称改为__main__
。
不通过其包含的包访问模块
还有一个额外的问题:模块的名称取决于它是从其所在目录“直接”导入还是通过软件包导入。仅当您在目录中运行Python并尝试将文件导入同一目录(或其子目录)时,这才有所不同。例如,如果您在目录中启动Python解释器package/subpackage1
,然后执行do import moduleX
,则其名称moduleX
将仅为moduleX
,而不是package.subpackage1.moduleX
。这是因为Python在启动时会将当前目录添加到其搜索路径中。如果它在当前目录中找到了要导入的模块,则不会知道该目录是软件包的一部分,并且软件包信息也不会成为模块名称的一部分。
一种特殊情况是,如果您以交互方式运行解释器(例如,只需键入python
并开始即时输入Python代码)。在这种情况下,该交互式会话的名称为__main__
。
现在,这是您的错误消息的关键所在:如果模块的名称没有点,则不认为它是包的一部分。文件实际在磁盘上的哪个位置都没有关系。重要的是它的名称是什么,它的名称取决于您如何加载它。
现在查看您在问题中包含的报价:
相对导入使用模块的名称属性来确定该模块在包层次结构中的位置。如果模块的名称不包含任何包信息(例如,将其设置为“ main”),则相对导入的解析就好像该模块是顶级模块一样,无论该模块实际位于文件系统上的哪个位置。
相对进口…
相对导入使用模块的名称来确定模块在包中的位置。当您使用类似的相对导入时from .. import foo
,点表示在包层次结构中增加了一些级别。例如,如果您当前模块的名称为package.subpackage1.moduleX
,则..moduleA
表示package.moduleA
。为了使a from .. import
起作用,模块的名称必须至少包含与import
语句中一样多的点。
…只是相对的
但是,如果模块的名称为__main__
,则不认为它在软件包中。它的名称没有点,因此您不能from .. import
在其中使用语句。如果您尝试这样做,则会收到“非包中的相对导入”错误。
脚本无法导入相对
您可能所做的是尝试从命令行运行moduleX
等。执行此操作时,其名称设置为__main__
,这意味着其中的相对导入将失败,因为它的名称不会显示它在软件包中。请注意,如果您从模块所在的同一目录运行Python,然后尝试导入该模块,也会发生这种情况,因为如上所述,Python会“过早”在当前目录中找到该模块,而没有意识到它是包装的一部分。
还请记住,当您运行交互式解释器时,该交互式会话的“名称”始终为__main__
。因此,您不能直接从交互式会话进行相对导入。相对导入仅在模块文件中使用。
两种解决方案:
如果您确实确实想
moduleX
直接运行,但是仍然希望将其视为软件包的一部分,则可以这样做python -m package.subpackage1.moduleX
。该命令-m
告诉Python将其作为模块而不是顶级脚本进行加载。或者,也许您实际上并不想运行
moduleX
,而只想运行其他脚本,例如myfile.py
,该脚本使用 inside函数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
)。
回答 1
这确实是python中的问题。混淆的根源是人们错误地将相对进口作为相对的进口,而不是。
例如,当您在faa.py中编写时:
from .. import foo
这具有只有一个意思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
回答 2
这是一个通用的配方,经过修改以适合作为示例,我现在使用它来处理以程序包形式编写的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)
这里的想法是这样的(请注意,这些在python2.7和python 3.x中的功能都相同):
如果从普通代码导入为常规软件包
import lib
或from lib import foo
作为常规软件包运行,__package
则islib
和__name__
islib.foo
。我们采用第一个代码路径,从.fileA
等导入。如果运行为
python lib/foo.py
,__package__
则将为None且__name__
将为__main__
。我们采用第二条代码路径。该
lib
目录已经存在,sys.path
因此无需添加它。我们从fileA
等导入如果在
lib
目录中以身份运行python foo.py
,则其行为与情况2相同。如果在
lib
as目录中运行python -m foo
,其行为类似于情况2和3。但是,lib
目录的路径不在in中sys.path
,因此我们在导入之前将其添加。如果我们先运行Python然后运行,则同样适用import foo
。(由于
.
是在sys.path
,我们并不真正需要添加此路径的绝对的版本。这是一个更深层次的包嵌套结构,我们想要做的from ..otherlib.fileC import ...
,有差别。如果你不这样做,就可以在sys.path
完全省略所有操作。)
笔记
仍然有一个怪癖。如果从外部运行整个过程:
$ python2 lib.foo
要么:
$ python3 lib.foo
行为取决于的内容lib/__init__.py
。如果存在并且为空,则一切正常:
Package named 'lib'; __name__ is '__main__'
但是,如果lib/__init__.py
本身导入,routine
以便可以routine.name
直接将导出为lib.name
,则会得到:
$ python2 lib.foo
Package named 'lib'; __name__ is 'lib.foo'
Package named 'lib'; __name__ is '__main__'
也就是说,该模块两次导入,一次是通过包导入的,然后是再次导入的,__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__'
该警告是新的,但警告说,有关的行为是不能。这就是所谓的双重导入陷阱的一部分。(有关其他详细信息,请参见问题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
也就是说,我们进行了sys.path
足够长的修改以实现导入,然后将其恢复原样(_i
如果且仅当我们添加的一个副本时,删除一个副本_i
)。
回答 3
因此,在与其他许多人讨论了这一问题之后,我遇到了Dorian B在本文中发布的注释,该注释解决了我在开发与Web服务一起使用的模块和类时遇到的特定问题,但我也想成为能够使用PyCharm中的调试器工具在编写代码时对其进行测试。要在独立的类中运行测试,我将在类文件末尾包含以下内容:
if __name__ == '__main__':
# run test code here...
但是如果我想在同一文件夹中导入其他类或模块,则必须将所有导入语句从相对符号更改为本地引用(即,删除点(。)。)但是在阅读了多里安的建议之后,我尝试了他的“一线”,它的工作!现在,我可以在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
if语句检查是否将这个模块作为main运行,或者是否在另一个被测试为main的模块中使用。也许这很明显,但是我在这里提供此说明,以防其他因上述相对导入问题而感到沮丧的人可以使用它。
回答 4
这是我不建议使用的一种解决方案,但在某些情况下可能根本不生成模块,这可能会很有用:
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()
回答 5
我有一个类似的问题,我不想更改Python模块的搜索路径,而需要从脚本中相对地加载模块(尽管“脚本不能与所有对象相对地导入”,正如BrenBarn上面很好地解释的那样)。
因此,我使用了以下技巧。不幸的是,它依赖于imp
从3.4版本开始就弃用的模块,而被取而代之importlib
。(这是否也可以用importlib
?我不知道。)不过,这种破解现在仍然有效。
从位于文件夹中的脚本访问moduleX
in的成员的示例:subpackage1
subpackage2
#!/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
较干净的方法似乎是修改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 *
回答 6
__name__
更改取决于所讨论的代码是在全局命名空间中运行还是作为导入模块的一部分运行。
如果代码不在全局空间中运行,则为__name__
模块名称。如果它在全局命名空间中运行-例如,如果您将其输入到控制台中,或者使用python.exe yourscriptnamehere.py
then __name__
成为脚本来运行该模块"__main__"
。
您将看到很多if __name__ == '__main__'
用于测试是否从全局命名空间运行代码的python代码 -这使您可以拥有一个兼用作脚本的模块。
您是否尝试过从控制台进行这些导入?
回答 7
@BrenBarn的回答说明了一切,但是如果您像我一样,可能需要一段时间才能理解。这是我的情况,以及@BrenBarn的答案如何适用于此,也许会对您有所帮助。
案子
package/
__init__.py
subpackage1/
__init__.py
moduleX.py
moduleA.py
使用我们熟悉的示例,并添加moduleX.py与..moduleA的相对导入。假设我尝试在导入moduleX的subpackage1目录中编写测试脚本,但随后得到了OP描述的可怕错误。
解
将测试脚本移至与package相同的级别,然后导入package.subpackage1.moduleX
说明
如前所述,相对导入是相对于当前名称进行的。当我的测试脚本从同一目录导入moduleX时,moduleX内的模块名称为moduleX。当遇到相对导入时,解释器无法备份程序包层次结构,因为它已经位于顶部
当我从上方导入moduleX时,moduleX内部的名称为package.subpackage1.moduleX,并且可以找到相对的导入
回答 8
相对导入使用模块的名称属性来确定该模块在包层次结构中的位置。如果模块的名称不包含任何包信息(例如,将其设置为“ main”),则相对导入的解析就好像该模块是顶级模块一样,无论该模块实际位于文件系统上的哪个位置。
在PyPi上编写了一些python程序包,它可能会对这个问题的查看者有所帮助。如果一个人希望能够运行一个python文件,而该文件包含一个包/项目中的包含上层包的导入文件,而又不直接位于导入文件的目录中,则该包文件可以作为解决方法。https://pypi.org/project/import-anywhere/
回答 9
为了使Python不再返回我“尝试以非包方式进行相对导入”。包/
init .py subpackage1 / init .py moduleX.py moduleY.py subpackage2 / init .py moduleZ.py moduleA.py
仅当您将相对导入应用于父文件时,才会发生此错误。例如,在moduleA.py中对“ print(name)”进行编码后,父文件已经返回main,因此该文件已经是main它无法进一步返回任何父包。包subpackage1和subpackage2的文件中需要相对导入,您可以使用“ ..”来引用父目录或模块。但是parent是如果已经是顶级程序包,则它不能在该父目录(程序包)之上。您向父母应用相对导入的此类文件只能与绝对导入应用一起使用。如果您使用绝对导入到父程序包中将不会出现错误,因为PYTHON PATH的概念定义了项目的顶层,即使您的文件位于子程序包中,python也会知道谁在程序包的顶层