前言
思考一中 简单介绍了import导入模块时的检索机制
然而在实际的导入时,我们经常会导入失败,尤其是在相对导入时。
下面就绝对导入和相对导入时发生的一些问题 进行记录和思考
文件目录结构
my_project/
|-- package1/
| |-- subpackage1/
| | |-- module2.py
| |-- module1.py
|-- package2/
| |-- subpackage2/
| | |-- module4.py
| |-- module3.py
|-- __init__.py
|-- main.py
|-- module5.py
各模块定义初始内容
# package1/module1.py
def func1():
print("This is func1 from module1 in package1")
# package1/subpackage1/module2.py
from ..module1 import func1
def func2():
print("This is func2 from module2 in subpackage1 of package1")
func1()
# package2/module3.py
def func3():
print("This is func3 from module3 in package2")
# package2/module4.py
from package1.module1 import func1
from ..module3 import func3
def func4():
print("This is func4 from module4 in subpackage2 of package2")
func1()
func3()
**注意: my_project 项目的运行环境为pycharm IDE 而非shell 环境 , **
my_project 设置成了内容根并加入到了PYTHONPATH (防止某些版本的解释器(嵌入式发行版)不会自动将当前目录加入sys.path中)
绝对导入
绝对导入的语法:import module/package (注意:模块/包路具体的书写方式 是相对于导入查找机制所查找到的文件路径而构造的)下面举例
例如:
-
系统内置包 已经内嵌在解释器中 所以无论何时何地 只需要import 包名 就行
-
第三方包(通过pip/其他方式安装),也是 import 包名 就行, 因为包的父目录会被解释器自动加入到sys.path路径中。
-
自定义的包(这里需要灵活构造), 针对当前的项目, 如果我运行main.py文件,解释器会自动将其所在的父目录加入sys.path中。所以如果我想在main 中导入 module1中的 func1 构造导入的路径就需要根据父目录 my_project 来构造,如下
# 运行脚本: main.py # mian.py 代码 import sys import package1.module1 package1.module1.func1() print(sys.path) print(dir()) print(dir(package1)) “”“ 输出: This is func1 from module1 in package1 ['E:\\work\\my_project', 'D:\\python\\python3.8.8\\python38.zip', 'D:\\python\\python3.8.8\\DLLs', 'D:\\python\\python3.8.8\\lib', 'D:\\python\\python3.8.8', 'E:\\work\\game\\mygame', 'E:\\work\\game\\mygame\\lib\\site-packages'] ['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'package1', 'sys'] ['__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'module1'] ”“” # 因为sys.path[0]如上所说会自动加载为当前脚本的所在的父目录,所以我们导入module1时书写的方式自然要以my_project为查找起点 # 如此构造 是告诉解释器 你去根据你的检索路径找吧, 看看有没有一个路径下会有package1/module1. 那么解释器挨个查找下来 # 当遇到sys.path[0] 时,在它下面发现了真有一个包为package1, 又因为它是一个包,继续遍历下去,在package1包下找到了 # module1.py, 然后就把 package1 加到了当前命名空间内, 而module1 则在package1的命名空间内。 所以你调用时也要按照 # 导入的顺序, # 理解了上述的,那么对绝对导入的构造也就举一反三了,不过再提一点,嫌调用太长 也可以以别名的方式去优化命名。例如 import sys import package1.module1 as XX XX.func1() print(sys.path) print(dir()) print(dir(XX)) “”“ 输出: This is func1 from module1 in package1 ['E:\\work\\my_project', 'D:\\python\\python3.8.8\\python38.zip', 'D:\\python\\python3.8.8\\DLLs', 'D:\\python\\python3.8.8\\lib', 'D:\\python\\python3.8.8', 'E:\\work\\game\\mygame', 'E:\\work\\game\\mygame\\lib\\site-packages'] ['XX', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'sys'] ['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'func1'] ”“” # 别名便于书写,但有时候乱起别名 不仅做不到见文知意,反而可能会引发命名冲突,需要自己考量
相对导入
其实大部分的时候 能以绝对导入的方式导入包/模块,就已经可以了。但如果一个大的项目,有个地方的名称变了,那么可能需要重构所有
引用到该包的路径了,此时相对导入的优势就体现了。
相对的核心在于 :
1. 相对指的是要导入的包相对于自己的包所在的位置,即以自身为起始点 查找到要使用的包 然后导入到自己所在的命名空间内 为已所用
2. 被导入模块能以相对导入的方式被找到,而这就是相对导入时常会出错所在,有时候明明考虑到了第一点 语法正确 却报找不到的问题
下面重点阐述 核心点2
在说明问题之前,我们需要知道 对于相对 import,可以通过查看 __package__ 才能决定是否能采用这种导入模式
只有__package__值为 [XX].[grandparent_
package].parent
_package
这种模式才能使用相对导入,
此时相对导入中的.
对应的就是parent_package, ..
对应的是 grandparent_
package
,...
对应的是XX 以此类推
值得注意的是如果当前模块即主程序入口(顶级模块)或者同级工作目录下的其他模块 是无法使用相对导入
1、当前模块为顶级模块时:解释器会自动将其所在的工作目录作为顶级目录,而不是作为一个包,此时__package__属性为None 则无法采用 相对导入
# 运行脚本: main.py
# main.py 代码1
import .module5
module5.func5()
"""
输出
File "E:/work/my_project/main.py", line 1
import .module5
^
SyntaxError: invalid syntax
"""
# main.py 代码2
print(__package__)
# 输出 None
2、此时它的同级目录下的模块的__package__属性为空,没有目录层级关系 也无法使用相对导入
# 运行脚本: main.py
# mian.py 代码
import module5
# module5.py 代码
def func5():
print("This is func5 from module5 in my_project")
print(__name__)
"""
main.py输出
"""
能导入的情况(举例说明):
# 运行脚本: main.py
# mian.py 代码
import package1.subpackage1.module2
import package2.subpackage2.module4
package1.subpackage1.module2.func2()
package2.subpackage2.module4.func4()
#package1/subpackage1/module2py 新代码
from ..module1 import func1
def func2():
print("This is func2 from module2 in subpackage1 of package1")
func1()
print("module2's __package__ is %s " % __package__)
# package2/module4.py 新代码
from package1.module1 import func1
from ..module3 import func3
def func4():
print("This is func4 from module4 in subpackage2 of package2")
func1()
func3()
print("module4's __package__ is %s " % __package__)
"""
main.py 输出
module2's __package__ is package1.subpackage1
module4's __package__ is package2.subpackage2
This is func2 from module2 in subpackage1 of package1
This is func1 from module1 in package1
This is func4 from module4 in subpackage2 of package2
This is func1 from module1 in package1
This is func3 from module3 in package2
"""
# 可以看出 module2 的__package__中 . 对应subpackage1, ..对应package1。 所以在module2 中 from ..module1 import func1
# 会从package1包中查找module1 然后导入其中的func1.
# module4 的导入也是类似
相对导入个人感悟先写到这。更深入的了解可以看看这两篇文章
标签:__,python,py,module1,导入,思考,package1,import From: https://blog.csdn.net/Agion_n/article/details/145261833