Python相对导入机制详解 - laike9m's blog
you cannot execute a file.py that has a relative import or you'll get a ValueError: Attempted relative import in non-package. 不要使用相对引用
把这个文件当作一个 module 载入,而不是 top-level 脚本
直接运行 .py 文件和 import 这个文件有很大区别
简单地说,直接运行 .py 文件和 import 这个文件有很大区别。Python 解释器判断一个 py 文件属于哪个 package 时并不完全由该文件所在的文件夹决定。它还取决于这个文件是如何 load 进来的(直接运行 or import)
(更严谨的说明:从 Python2.6 开始,在做 package-resolution 时,module 的 “名字” 并不完全等于 __name__ 属性,还和 __package__ 属性有关。这也是为什么上文中我一直尽量避免用 __name__ 来指代 module 的名字。从 python2.6 开始,一个 module 的 “名字” 实际上是 __package__ + '.' + __name__, 或者直接就是 __name__,如果 __package__ 是 None 的话)
有两种方式加载一个 py 文件:
1. 作为 top-level 脚本或者作为 module。前者指的是直接运行脚本,比如 python myfile.py。
有且只能有一个 top-level 脚本,就是最开始执行的那个(比如 python myfile.py 中的 myfile.py,译者注)。
2. 如果执行 python -m myfile,或者在其它 py 文件中用 import 语句来加载,那么它就会被当作一个 module。
当一个 py 文件被加载之后,它会被赋予一个名字,保存在 __name__ 属性中。如果是 top-level 脚本,那么名字就是 __main__。如果是作为 module,名字就是把它所在的 packages/subpackages 和文件名用 . 连接起来。
例如,moduleX 被 import 进来,它的名字就是 package.subpackage1.moduleX。如果 import 了 moduleA,它的名字是 package.moduleA。如果直接运行 moduleX 或 moduleA,那么名字就都是 __main__ 了。
前面说了,如果 module 的名字是 __main__,那么 Python 就不认为它属于某个 package。由于名字里不包含点,所以在这个 py 文件中 from .. import 语句无法正常工作。试图执行这条语句就会报 "relative-import in non-package" 错误。
另一个令人担忧的问题是,一个 module 的名字取决于它是直接从它所在的文件夹 import 还是通过某个 package import 的。不过只有当你在某个路径中运行 Python 并试图从当前文件夹 import 一个 py 文件时,才需要关注它们的不同。例如,在路径 pacakge/subpackage1 中运行 python 解释器,然后脚本中有 import moduleX 这个语句,此时 moduleX 的名字正是 moduleX,而不是 package.subpackage1.moduleX。这是因为 Python 解释器在启动时把当前路径(这里答案写的不准确,其实加入的是 top-level 脚本的路径,因为两者在这种状况下相同,所以也并不算错。译者注)加入了它的搜索路径 (sys.path);如果发现要 import 的 module 就在当前路径,那么 Python 解释器就不知道当前路径是属于哪个 package 的,所以 package 的信息就不会成为 module 的名字的一部分。
关于你遇到的错误信息,关键点来了:如果一个 module 的名字中没有点(即 package.subpackage1 中的那个点,译者注),那么它就被认为不属于任何一个 package。文件在磁盘上的位置在哪里都不影响,唯一起决定作用的就是 module 的名字,而这又取决于它是如何被加载的。
relative import 使用 module 的名字来决定它是否属于一个 package,属于哪个 package。当你使用这种 relative import from .. import foo,其中的点的数量代表了 package 结构中的某个层次。例如,如果当前 module 的名字是 package.subpackage1.moduleX,那么 ..moduleA 代表 package.moduleA。为了让形如 from .. import的这种导入能够正常工作,module 的名字里的点数量应当至少和 import 语句中一样多。
你犯的错误可能是从命令行运行 moduleX 或类似的操作。当你执行这个操作,moduleX 的名字被设置成 __main__,所以 relative imports 失败了,因为不包含 package 信息。正如前面说的,如果在同一个路径里 import 一个文件,这时 module 的名字就是文件名,不包含 package 信息,所以相对导入也会失败。
(无法相对导入的问题)有两个解决方法。如果你真的想直接运行 moduleX,同时又希望它被当作所在 package 的一部分,可以这么做:python -m package.subpackage.moduleX。-m 参数告诉 Python 解释器,把这个文件当作一个 module 载入,而不是 top-level 脚本。
如果你并不想直接运行 moduleX,而是想在另一个文件比如 myfile.py 中使用 moduleX 中定义的函数,那么解决方法是把 myfile.py 文件挪到另一个地方,只要不在 moduleX 所属的 package 的文件夹里就行。然后在 myfile.py 中执行 from package.moduleA import spam,就能正常工作了。
注意,不论哪种解决方案,都需要 package 的路径(上文中的 package)在 python 的搜索路径也就是 sys.path里。如果不在,那么就无法使用这个 package 中的任何文件。
.
└── my_package
├── __init__.py
├── module_t.py
├── subpackage1
│ ├── __init__.py
│ └── module_x.py
└── subpackage2
├── __init__.py
├── module_z.py
不论在哪级目录下执行module_x.py文件, 其中的sys.path总是'/home/prac/my_package/subpackage1', 即top-level文件所在的文件夹
体现不同的执行目录是由__file__变量,基于当前路径的目标相对路径
[root@controller:/home/prac/my_package/subpackage1]$ python module_x.py
__file__为module_x.py
[root@controller:/home/prac]$ python my_package/subpackage1/module_x.py
__file__为my_package/subpackage1/module_x.py
__name__变量体现基于sys.path找到要执行的python文件,即sys.path+ __name__
如果是top-level文件,则__name__='__main__'
如果在文件中增加
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
print sys.path
from subpackage2.module_z import spam
后续from import会基于改变后的路径寻找
['/home/prac/my_package/subpackage1', '/usr/lib/python2.7/site-packages/robotframework-3.0.2-py2.7.egg', '/usr/lib64/python27.zip', '/usr/lib64/python2.7', '/usr/lib64/python2.7/plat-linux2', '/usr/lib64/python2.7/lib-tk', '/usr/lib64/python2.7/lib-old', '/usr/lib64/python2.7/lib-dynload', '/usr/lib64/python2.7/site-packages', '/usr/lib/python2.7/site-packages', './prac/my_package/subpackage1/..']
增加的是top-level文件的目录的相对路径
结论:在top-level里不可以直接使用相对路径,除非显示改变sys.path
网友评论