第十亿次相对进口

  • 问题:
  • 我来过这里:

    还有很多我没有复制的网址,有些在SO上,有些在其他网站上,当时我以为我会很快找到解决方案

    永远反复出现的问题是:在Windows7、32位Python2.7.3中,如何解决“试图在非包中进行相对导入”的消息?我在pep-0328上建立了一个完全相同的软件包副本:

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

    导入是从控制台完成的

    我确实在它们的相应模块中创建了名为spam和eggs的函数。当然,这没用。答案显然在我列出的第四个网址上,但对我来说都是校友。在我访问的一个网址上有这样的回复:

    相对导入使用模块的name属性来确定该模块在包层次结构中的位置。如果模块的名称不包含任何包信息(例如,它被设置为“main”),则相关导入将被解析为如同模块是顶级模块一样,而不管模块实际位于文件系统中的哪个位置

    上面的回答看起来很有希望,但对我来说都是象形文字。所以我的问题是,如何使Python不返回“在非包中尝试相对导入”?答案应该是-m

    有人能告诉我为什么Python给出错误消息,它的意思是“non-package”,为什么和如何定义一个“package”,以及用足够容易让幼儿园学生理解的术语给出的精确答案。在

  • 答案:
  • 脚本与模块

    这里有个解释。直接从Python导入的文件和直接从其他地方导入的文件有很大的区别。仅仅知道文件在哪个目录并不能确定Python认为它在哪个包中。这还取决于如何将文件加载到Python中(通过运行或导入)

    有两种加载Python文件的方法:作为顶级脚本,或作为

    命名

    当一个文件被加载时,它会被赋予一个名称(存储在它的\uuname\u属性中)。如果它是作为顶级脚本加载的,它的名称是。如果它是作为模块加载的,它的名称是文件名,前面是它所属的任何包/子包的名称,用点分隔

    例如,在你的例子中:

    package/
    __init__.py
    subpackage1/
    __init__.py
    moduleX.py
    moduleA.py

    如果导入了moduleX(注意:imported,而不是直接执行),则其名称将为包.子包1.moduleX。如果导入moduleA,它的名称将是模块A包。但是,如果您从命令行直接运行moduleX,那么它的名称将改为moduleA,如果您直接从命令行运行moduleA,那么它的名称将是moduleA。当一个模块作为顶级脚本运行时,它将失去它的正常名称,取而代之的是\uumain\

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

    还有一个问题:模块的名称取决于它是从它所在的目录“直接”导入的,还是通过包导入的。只有在目录中运行Python并尝试导入同一目录(或其子目录)中的文件时,这才有区别。例如,如果在目录package/subpackage1中启动Python解释器,然后执行import moduleX,则moduleX的名称将只是moduleX,而不是包.子包1.moduleX。这是因为Python在启动时会将当前目录添加到其搜索路径中;如果在当前目录中找到要导入的模块,它将不知道该目录是包的一部分,包信息也不会成为模块名称的一部分

    一种特殊情况是,如果您交互式地运行解释器(例如,只需键入python并开始动态地输入python代码)。在本例中,该交互会话的名称为\uuMain\

    下面是错误消息的关键点:如果一个模块的名称没有点,它就不被认为是包的一部分。文件在磁盘上的实际位置并不重要。重要的是它的名称是什么,它的名称取决于您如何加载它

    现在看看你在问题中引用的话:

    相对导入使用模块的name属性来确定该模块在包层次结构中的位置。如果模块的名称不包含任何包信息(例如,它被设置为“main”),则相关导入将被解析为如同模块是顶级模块一样,而不管模块实际位于文件系统中的哪个位置

    相对进口…

    相对导入使用模块的名称来确定它在包中的位置。当您使用相对导入时,如from。。import foo,点表示在包层次结构中向上提升一些级别。例如,如果当前模块的名称是包.子包1.moduleX,然后是。moduleA的意思是模块A包。对于来自。。import要工作,模块名称必须至少具有与import语句中相同数量的点

    。。。只在包中是相对的

    但是,如果您的模块名是\uumain\,则不认为它在一个包中。它的名称没有点,因此不能使用from。。在其中导入语句。如果您尝试这样做,您将得到“相对导入非包”错误

    脚本无法导入相对

    您可能会尝试从命令行运行moduleX或类似的命令。当您执行此操作时,它的名称被设置为\uuMain\,这意味着它内部的相对导入将失败,因为它的名称不会显示它在包中。请注意,如果从模块所在的同一目录运行Python,然后尝试导入该模块,也会发生这种情况,因为如上所述,Python将在当前目录中“太早”找到该模块,而没有意识到它是包的一部分

    还请记住,当您运行交互式解释器时,该交互式会话的“名称”始终是\uumain\。因此,您不能直接从交互式会话进行相对导入。相对导入仅用于模块文件中

    两种解决方案:

    如果您确实希望直接运行moduleX,但仍然希望将其视为包的一部分,那么可以使用python-m包.子包1.moduleX。-m告诉Python将其作为模块加载,而不是作为顶级脚本加载

    或者您实际上并不想运行moduleX,您只想运行其他脚本,比如我的文件.py,它在moduleX中使用函数。如果是这样,请输入我的文件.py其他地方不在目录中,并运行它。如果在内部我的文件.py您可以从模块A包导入垃圾邮件,它将正常工作

    注意事项

    对于这些解决方案中的任何一个,必须可以从Python模块搜索路径(package)访问包目录(搜索路径)。否则,您将无法可靠地使用包中的任何内容

    从Python2.6开始,用于包解析的模块的“名称”不仅由其属性决定,还由包的属性决定。这就是为什么我避免使用显式符号\uuname_u来引用模块的“name”。由于Python2.6,模块的“name”实际上是\uuPackage_U+''.'+\uuName\,或者如果\uuuPackage_UNone,那么模块的“name”实际上就是\uuuName\