门户网站构建国家备案网
前言
Python中的包管理,一直是我比较困惑的一点。
- python导入另一个文件,会不会执行另一个文件的?
- python是怎么找到具体的包的?包名不会重复吗?
- 目录层级结构比较复杂的时候,怎么导入包?
简单实例
同级目录下,main.py可以按照如下方式导入mymath.py中的变量和函数
目录结构如下
- python_package
--main.py
--mymath.py
# mymath.py
PI = 3.14def add(a,b):return a+b
# main.py
import mymathprint(mymath.add(1, 2))
import本质是什么?
那么我们需要深入研究一下,import到底做了什么呢?
首先我们要看看mymath到底是个什么?
print(type(mymath))
# <class 'module'>
可以看到mymath其实是一个变量,指向了module类的实例。所以import就是生成了一个module实例,然后赋值给了变量mymath
于是我们就像变量一样使用它,如下
mm = mymath
print(mymath.add(1, 2))
print(mm.add(1, 2))
相当于给mymath起了一个别名,因为这种起别名的方式还挺常见的,所以Python有特殊的语法专门做这个事
# python在创建module后,就将其赋值给mm变量,然后就没有mymath变量了
import mymath as mm
print(mm.add(1, 2))
ok,我们知道了mymath是指向一个module实例的变量
那么import是怎么生成这个module实例的呢?
我们在遇到import实例的时候,python首先会查询,mymath有没有被import过。
如果没有:python就会将mymath.py读取到内存中,并运行。
而如果已经被import过了就找到当时创建的module,并把它直接赋值给import后面的变量。
所以如果import了两次同样的文件,那这个文件只会在被第一次import时,运行一次。
我们来验证一下
# 在mymath.py中加入打印
print("exeute mymath.py")
PI = 3.14def add(a,b):return a+b
# main.py多次引入
import mymath as mm
import mymath as mm2
# 分别打印id
print(id(mm))
print(id(mm2))
exeute mymath.py
2509003175824
2509003175824
可以看到mymath.py只被运行了一次,且mm和mm2的ID是相同的。说明他们指向同一个module对象
当mymath.py运行时,import创建的module就会成为mymath.py的全局命名空间。所以说mymath.py中定义的全局变量,就会被定义在import创建的module中
我们可以通过dir()函数来看到mm定义的内容
# main.py
import mymath as mm
import mymath as mm2print(dir(mm))
['PI', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'add']
可以看到PI和add都在这个全局变量中
现在我们来个骚操作
# mymath.py
print("exeute mymath.py")
PI = 3.14def add(a,b):return a+bdef add_global(name,value):g = globals()g[name] = value
# main.py
import mymath as mm
import mymath as mm2mm.add_global("E",2.71)
print(mm.E)
mymath中本来是没有E这个变量的,但是通过调用方法给他添加了这个变量后,就有了。
这说明mm就是mymath.py运行的全局命令空间。
python import还有另外一种写法。
# main.py
from mymath import PI
这样做还是会为mymath创建module并且运行mymath.py
但这次不会把module赋值给mymath变量了,而是把PI的值直接赋值给当前命名空间的PI变量
注意这里是一个赋值的操作,赋值之后mymath.py中的PI就和当前文件中的PI没有任何的关系了
也就是说,如果我们修改当前的PI值是不会修改mymath.py中的PI值的
from mymath import PI
import mymath as mmPI = 9999
print(f"PI in main: {PI}")
print(f"PI in mymath: {mm.PI}")
exeute mymath.py
PI in main: 9999
PI in mymath: 3.14
所以说import的本质就是创建命名空间,也就是module,然后对变量进行赋值的一个过程
代码从哪找?
好,现在我们创建了module就应该找相应的Python文件来运行了。
那么从哪里找呢?
就是从sys.path中查找
from mymath import PI
import mymath as mm
import sysprint(sys.path)
['d:\\pythonProject\\python_package', 'D:\\Develop\\python\\python310\\python310.zip', 'D:\\Develop\\python\\python310\\DLLs', 'D:\\Develop\\python\\python310\\lib', 'D:\\Develop\\python\\python310', 'D:\\Develop\\python\\python310\\lib\\site-packages']
# 可以看到sys.path的第一项,就是main脚本所在的目录
# 还包含了python自带的库的目录
# 以及安装的第三方的库的目录 D:\\Develop\\python\\python310\\lib\\site-packages
sys.path就是一个普通的list,所以我们是可以用代码对它进行更改的。
当然除了通过代码,我们还可以在运行Python的时候,指定环境变量PYTHONPATH来改变sys.path的值
如下
PYTHONPATH=D:\a\b\c python main.py$env:PYTHONPATH="D:\\a\\b\\c"; python main.pyPS D:\pythonProject\python_package> $env:PYTHONPATH="D:\\a\\b\\c"; python main.py
exeute mymath.py
['D:\\pythonProject\\python_package', 'D:\\a\\b\\c', 'D:\\Develop\\python\\python310\\python310.zip', 'D:\\Develop\\python\\python310\\DLLs', 'D:\\Develop\\python\\python310\\lib', 'D:\\Develop\\python\\python310', 'D:\\Develop\\python\\python310\\lib\\site-packages', 'D:\\Develop\\python\\python310\\lib\\site-packages\\win32']
可以看到D:\\a\\b\\c已经被加到sys.path变量中了。
于是在import时python就可以从这些路径去寻找mymath.py文件了
如果找到了皆大欢喜,如果找不到python就会去寻找mymath的目录
mymath目录中往往还有一个__init__.py,这时python就会去运行__init__.py
带有__init__.py目录的优先级是比文件要高的,

execute __init__.py
['D:\\pythonProject\\python_package', 'D:\\Develop\\python\\python310\\python310.zip', 'D:\\Develop\\python\\python310\\DLLs', 'D:\\Develop\\python\\python310\\lib', 'D:\\Develop\\python\\python310', 'D:\\Develop\\python\\python310\\lib\\site-packages', 'D:\\Develop\\python\\python310\\lib\\site-packages\\win32']
可以看到,我们的程序只打印出了__init__.py的输出。这说明我们的程序确实加载了__init__.py文件,没有加载mymath.py文件
而这个目录其实就是所谓的package,
注意:无论有没有__init__.py文件,Python都会加载这个目录,只不过如有没有__init__.py这个文件,Python就不运行任何代码了。但是module还是会照常被创建。
我们试试直接创建一个目录mydir
# main.py
import mydirprint(mydir)
<module 'mydir' (<_frozen_importlib_external._NamespaceLoader object at 0x00000170F723DA80>)>
可以看到mydir还是可以被正常的加载
Python也是支持子目录的
我们在mydir下创建subdir。然后我们就可以用如下方式导入子目录
# main.py
import mydir.subdirprint(mydir)
print(mydir.subdir)
<module 'mydir' (<_frozen_importlib_external._NamespaceLoader object at 0x000001D607B1DA50>)>
这时Python会做两件事情
- 首先在当前的命名空间中导入
mydir - 然后再在
mydir的命名空间中导入subdir
我们可以使用dir打印一下两个命名空间,看看里面分别都有什么
# main.py
import mydir.subdirprint(dir())
print(dir(mydir))['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'mydir']
['__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'subdir']
可以看到在第一个命名空间中有mydir,在第二个命令空间中有subdir
当然我们也可以用一下方式导入子目录
这样就可以将subdir直接导入到当前命名空间中,但这样依然会在mydir的命名空间中也导入subdir
from mydir import subdir
import mydirprint(dir())
print(dir(mydir))
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'mydir', 'subdir']
['__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'subdir']
可以看到当前命令空间和mydir命名空间中都有了subdir
relative import 相对导入
我们需要在mydir目录中的a.py引入subdir
import mydir.subdir
from . import subdir
总结
import mymath导入包,会在os.path列表下以此去找对应的目录或者文件。目录下带有__init__.py的优先级大于文件。- 带有
__init__.py的目录,则被称为package包 os.path是一个地址路径列表,依次查找对应的目录或者文件以找到的第一个包为准。import mymath如果是目录则会执行mymath目录下的__init__.py文件,如果是文件mymath.py则执行该文件,然后创建module类的实例赋值给mymath变量dir()可以查看命名空间中有哪些变量。
