Python引入原理分析
Python中import引入文件分为绝对引入和相对引入。
绝对引入
搜索PYTHONPATH
环境变量目录里的所有文件,如果存在就引入,不存在就报错
这么就有一个问题,我们经常会有这种需求: 引入一个同级目录的文件
当不在一个package中时,通常我们会使用绝对引入来引入这个包,如:
src ├ a.py └ b.py
如果想要在a.py
中引入b.py
,通常会使用import b
或from b import x
为什么这里绝对引入可以使用呢?
打印出sys.path
后可以发现,当我们使用python a.py
时,程序会自动将a.py
所在的目录加入到PYTHONPATH
中
这样绝对引入时,会在当前的代码目录找到b.py
并成功引入
相对引入
相对引入只能使用在package
中
相对于绝对引入必须要写明路径,相对引入可以使用.
和..
来引入同级或上级目录的内容,相对来说非常方便
当由于某些需要改动了模块间的关系时,改动也比较小
我们可以建立一个如下的文件夹来加深理解
根目录: root.py
, root2.py
, pack/
root.py
import site import sys print("="*10) print("这是root文件") print("运行参数", *sys.argv) print("__name__\t", __name__) print("__package__\t", __package__) print("__file__\t", __file__) for path in sys.path: print(path) print("="*10) try: from root2 import p1 p1() except Exception as e: print("Error", *e.args) try: from pack.fun import p2, p3 p2() except Exception as e: print("Error", *e.args)
root2.py
import sys print("="*10) print("这是root2文件") print("运行参数", *sys.argv) print("__name__\t", __name__) print("__package__\t", __package__) print("__file__\t", __file__) print("="*10) def p1(): print("p1")
pack/
目录: __init__.py
, fun.py
, fun2.py
__init__.py
import sys print("="*10) print("这是包的__init__文件") print("运行参数", *sys.argv) print("__name__\t", __name__) print("__package__\t", __package__) print("__file__\t", __file__) print("="*10)
fun.py
import sys from .fun2 import p3 print("="*10) print("这是fun文件") print("运行参数", *sys.argv) print("__name__\t", __name__) print("__package__\t", __package__) print("__file__\t", __file__) print("="*10) def p2(): print("p2") p3()
fun2.py
import sys print("="*10) print("这是fun2文件") print("运行参数", *sys.argv) print("__name__\t", __name__) print("__package__\t", __package__) print("__file__\t", __file__) print("="*10) def p3(): print("p3")
输出结果:
========== 这是root文件 运行参数 e:\pytest\src\root.py __name__ __main__ __package__ None __file__ e:\pytest\src\root.py e:\pytest\src C:\Users\kongchenhao\AppData\Local\Programs\Python\Python37-32\python37.zip C:\Users\kongchenhao\AppData\Local\Programs\Python\Python37-32\DLLs C:\Users\kongchenhao\AppData\Local\Programs\Python\Python37-32\lib C:\Users\kongchenhao\AppData\Local\Programs\Python\Python37-32 C:\Users\kongchenhao\AppData\Local\Programs\Python\Python37-32\lib\site-packages ========== ========== 这是root2文件 运行参数 e:\pytest\src\root.py __name__ root2 __package__ __file__ e:\pytest\src\root2.py ========== p1 ========== 这是包的__init__文件 运行参数 e:\pytest\src\root.py __name__ pack __package__ pack __file__ e:\pytest\src\pack\__init__.py ========== ========== 这是fun2文件 运行参数 e:\pytest\src\root.py __name__ pack.fun2 __package__ pack __file__ e:\pytest\src\pack\fun2.py ========== ========== 这是fun文件 运行参数 e:\pytest\src\root.py __name__ pack.fun __package__ pack __file__ e:\pytest\src\pack\fun.py ========== p3 p2
可以看出,这样写是可以正常运行的,这时候,如果我们直接执行fun.py
,会报错:
ModuleNotFoundError: No module named '__main__.fun2'; '__main__' is not a package
这个很容易就可以看出来,相对引入的相对是对于__main__
这个常量的
当我们使用引入一个包后,__main__
的值就是当前包相对于运行的主程序的路径,自然后面跟上.xxx
就变成了绝对引入
因此,虽然相对引入比绝对引入更早地加入到Python中,但是相对引入更类似于绝对引入的一个语法糖。
因此,实际上是不能够只执行一个项目中某个Package中的一个文件进行临时性的测试的(如果该文件有引入上层目录或同级目录下的内容)
但是可以通过变通的方式解决该问题:
- 对于有引入上级目录的内容,只需要将原来的函数入口的路径加入到
PYTHONPATH
中即可。通常来说,尽管可以使用sys.path
加入,但是为了可移植性,应该使用命令行来动态加入。
如powershell中可使用:$env:pythonpath=$env:pythonpath+';'+$pwd;python xx.py
对于VSCode,可以直接使用"terminal.integrated.env.windows": { "PYTHONPATH": "${workspaceFolder}" }
- 对于有同级文件的引入,由于在引入包中,__name__是包路径,但是直接运行文件时,
__name__
是__main__
。因此一方面可以使用绝对引入来引入同级包(但是这样也会造成移植性降低);同时也可以判断__name__
的值来实现该方法。当__name__
为__main__
时使用相对引入;否则使用绝对引入。
这样就实现了在Python中不影响整个项目运行的情况下,执行一个包中的一个文件进行测试等操作。