第十亿次相对import

我来过这里

  • http://www.python.org/dev/peps/pep-0328/
  • http://docs.python.org/2/tutorial/modules.html#packages
  • Python包:相对导入
  • python相对导入示例代码不起作用
  • 相对python导入的最终答案
  • Python中的相对导入
  • Python:禁用相对导入

以及大量的url,我没有复制,有些是在其他网站上,有些在我以为我会很快得到解决scheme。

永远反复出现的问题是:对于Windows 7,32位Python 2.7.3,我该如何解决这个“试图在非包装中进行相对导入”的消息? 我在pep-0328上构build了一个完整的包:

package/ __init__.py subpackage1/ __init__.py moduleX.py moduleY.py subpackage2/ __init__.py moduleZ.py moduleA.py 

我确实在适当的模块中创build了名为垃圾邮件和鸡蛋的函数。 自然,它没有工作。 答案显然是在我列出的第四个URL中,但是这些都是我的校友。 在我访问的其中一个url上有这样的回应:

相对导入使用模块的名称属性来确定模块在包层次结构中的位置。 如果模块的名称不包含任何包信息(例如,它被设置为“main”),则无论模块在文件系统上的实际位置如何,相对导入都将被parsing为模块是顶级模块。

上面的回应看起来很有希望,但对我来说这全是象形文字。 所以我的问题,我怎么让Python不返回给我“试图在非包的相对导入”? 有一个答案,涉及-m,据说。

有人可以告诉我为什么Python给出了这个错误信息, 它是什么意思,非包装! ,为什么以及如何定义一个“一揽子”,并且准确的答案对于一个幼儿园的学生来说是很容易理解的

编辑:导入是从控制台完成的。

脚本与模块

这是一个解释。 简短的版本是,直接运行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 (注: 导入 ,不能直接执行),它的名字就是package.subpackage1.moduleX 。 如果你导入了moduleA ,它的名字是package.moduleA 。 但是,如果直接从命令行运行 moduleX ,则其名称将改为__main__ ,如果moduleA直接运行moduleA ,则其名称将为__main__ 。 当一个模块作为顶级脚本运行时,它将失去正常的名字,而它的名字是__main__

不通过其包含的包访问模块

还有一个额外的皱纹:模块的名称取决于它是从目录中“直接”导入,还是通过包导入。 如果你在一个目录中运行Python,这只会有所不同,并且试图在同一个目录(或者它的子目录)中导入一个文件。 例如,如果在目录package/subpackage1启动Python解释器,然后执行import moduleX ,则import moduleX的名称将仅为moduleX ,而不是package.subpackage1.moduleX 。 这是因为Python在启动时将当前目录添加到searchpath中; 如果在当前目录中find待导入模块,则不知道该目录是包的一部分,包信息将不会成为该模块名称的一部分。

一个特殊情况是,如果你以交互方式运行解释器(例如,只需键入python并开始inputPython代码)。 在这种情况下,该交互式会话的名称是__main__

现在,这里是你的错误信息的关键: 如果一个模块的名字没有点,它不被认为是一个包的一部分 。 这个文件在磁盘上的位置并不重要。 重要的是它的名字,它的名字取决于你如何装载它。

现在看看你的问题中包含的报价:

相对导入使用模块的名称属性来确定模块在包层次结构中的位置。 如果模块的名称不包含任何包信息(例如,它被设置为“main”),则无论模块在文件系统上的实际位置如何,相对导入都将被parsing为模块是顶级模块。

相对导入…

相对导入使用模块的名称来确定它在包中的位置。 当你使用from .. import foo这样的相对导入时,这些圆点表示要在包层次结构中增加一些级别。 例如,如果你当前模块的名字是package.subpackage1.moduleX ,那么..moduleA意味着package.moduleA 。 对于from .. import到工作,模块的名称必须至less具有import语句中的点数。

只是在一个包中是相对的

但是,如果您的模块的名称是__main__ ,则不会将其视为包。 它的名字没有点,因此你不能使用它里面的from .. import语句。 如果您尝试这样做,您将会得到“非包装中的相对导入”错误。

脚本不能导入相对

你可能做的是你试图从命令行运行moduleX或类似的东西。 当你这样做的时候,它的名字被设置为__main__ ,这意味着它内部的相对导入将会失败,因为它的名字没有显示它在一个包中。 请注意,如果您从模块所在的同一目录运行Python,然后尝试导入该模块,也会发生这种情况,因为如上所述,Python会在当前目录中“太早”地发现模块而不知道它是包的一部分。

另外请记住,当您运行交互式解释器时,交互式会话的“名称”总是__main__ 。 因此, 您不能直接从交互式会话进行相对导入 。 相对导入只能在模块文件中使用。

两种解决scheme

  1. 如果你真的想直接运行moduleX ,但你仍然希望它被认为是包的一部分,你可以做python -m package.subpackage.moduleX-m告诉Python将其作为模块加载,而不是作为顶级脚本。

  2. 或者你也许并不想运行 moduleX ,你只是想运行一些其他的脚本,比如myfile.py ,它使用 moduleX函数。 如果是这样的话,把myfile.py 放在别的地方不要放在package目录中 – 然后运行它。 如果在myfile.py里面,你可以像from package.moduleA import spam ,它会正常工作。

笔记

  • 对于这些解决scheme中的任何一个,必须可以从Python模块searchpath( sys.path )访问软件包目录(在您的示例中为软件包)。 如果不是的话,你将无法可靠地使用包装中的任何东西。

  • 从Python 2.6开始,模块用于包parsing目的的“名称”不仅由__name__属性决定,还由__package__属性决定。 这就是为什么我避免使用显式符号__name__来引用模块的“名称”。 由于Python 2.6模块的“名称”是有效的__package__ + '.' + __name__ __package__ + '.' + __name__ ,或者只有__name__如果__package__None 。)

以下是我不推荐的一种解决scheme,但在某些情况下模块根本不会生成可能会有用:

 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() 

__name__根据所涉及的代码是在全局名称空间中运行还是作为导入模块的一部分而变化。

如果代码没有在全局空间中运行, __name__将是模块的名称。 如果它在全局名称空间中运行 – 例如,如果您将其键入控制台,或者使用python.exe yourscriptnamehere.py作为脚本运行模块,则__name__变为"__main__"

if __name__ == '__main__'来testing代码是否从全局名称空间运行,您将看到很多python代码 – 这使您可以将模块兼容为脚本。

你有没有试图从控制台进行这些import?

下面是一个通用的配方,作为例子进行了修改,我现在正在使用它来处理编写为包的Python库,这些库包含相互依赖的文件,我希望能够逐个testing其中的一部分。 让我们调用这个lib.foo并说它需要访问函数f1f2 lib.fileB ,以及类Class3 lib.fileB

我已经包括了一些print电话来帮助说明这是如何工作的。 在实践中,你会想要删除它们(也可能是from __future__ import print_function行)。

当我们真的需要在sys.path插入条目时,这个特殊的例子太简单了。 (请参阅Lars的答案 ,当我们需要它的时候,当我们有两个或更多级别的包目录,然后我们使用os.path.dirname(os.path.dirname(__file__)) – 但它不在这里也真的受到了伤害 。) if _i in sys.pathtesting中没有if _i in sys.path也是足够安全的。 但是,如果每个导入的文件插入相同的path(例如,如果fileAfileB都想从包中导入实用程序),则会fileA使用相同的pathfileA 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中相同):

  1. 如果作为import lib运行,或者from lib import foo作为常规程序包从普通代码导入, __packagelib__name__lib.foo 。 我们采用第一个代码path,从.fileA等导入。

  2. 如果以python lib/foo.py运行, __package__将为None, __name__将为__main__

    我们采取第二个代码path。 lib目录已经在sys.path所以不需要添加它。 我们从fileA等导入

  3. 如果在python foo.pylib目录下运行,其行为与案例2相同。

  4. 如果以python -m foolib目录下运行,其行为与情况2和3相似。但是, lib目录的path不在sys.path ,因此我们在导入之前添加它。 如果我们运行Python然后import foo

    (因为在sys.path ,我们并不需要在这里添加绝对path的版本,这是一个更深的包嵌套结构,我们想from ..otherlib.fileC import ...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以便它可以直接将lib.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__' 

警告是新的,但警告的行为不是。 这是一些所谓的双重import陷阱的一部分 。 (有关更多详细信息,请参阅issue 27487。 )Nick Coghlan说:

下一个陷阱存在于所有当前版本的Python中,包括3.3,可以归纳为以下一般原则:“不要将包目录或包中的任何目录直接添加到Pythonpath中”。

请注意,虽然我们在这里违反了这个规则,但是我们在被加载的文件没有被加载作为包的一部分时才这样做,我们的修改是专门devise的,以允许我们访问该包中的其他文件。 (而且,正如我所指出的,我们可能根本不应该为单层包而这样做。)如果我们希望变得更加干净,我们可以将其重写为:

  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一个副本,当且仅当我们添加了_i一个副本)。

我有一个类似的问题,我不想改变Python模块searchpath,并需要从脚本相对加载模块(尽pipe“脚本不能全部导入相对” ,正如上面BrenBarn所解释的)。

所以我用了下面的黑客。 不幸的是,它依赖于从3.4版开始被弃用的imp模块,以便importlib 。 ( importlib也可以吗?我不知道。)尽pipe如此,黑客还是可以工作的。

从位于subpackage2文件夹中的脚本访问subpackage1moduleX成员的moduleX

 #!/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 

更清洁的方法似乎是修改用于加载模块的sys.path,如Federico所述。

 #!/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 *