当前位置: 首页 > news >正文

【Python】包管理,弄明白import,package,module

前言

Python中的包管理,一直是我比较困惑的一点。

  1. python导入另一个文件,会不会执行另一个文件的?
  2. python是怎么找到具体的包的?包名不会重复吗?
  3. 目录层级结构比较复杂的时候,怎么导入包?

简单实例

同级目录下,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']

可以看到PIadd都在这个全局变量中

现在我们来个骚操作

# 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会做两件事情

  1. 首先在当前的命名空间中导入mydir
  2. 然后再在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

总结

  1. import mymath导入包,会在os.path列表下以此去找对应的目录或者文件。目录下带有__init__.py的优先级大于文件
  2. 带有__init__.py的目录,则被称为package
  3. os.path是一个地址路径列表,依次查找对应的目录或者文件以找到的第一个包为准。
  4. import mymath如果是目录则会执行mymath目录下的__init__.py文件,如果是文件mymath.py则执行该文件,然后创建module类的实例赋值给mymath变量
  5. dir()可以查看命名空间中有哪些变量。
http://www.dtcms.com/a/354045.html

相关文章:

  • 复杂网络环境实测:主流云VR产品性能对比——平行云LarkXR突破网络限制 引领云VR技术新高度
  • 记住密码管理器
  • 在Eclipse中配置Tomcat
  • 终端美化:Windows11 下 安装 WSL 并使用好看的的 zsh 主题
  • 【图论】最短路算法
  • 802.11ax上行OFDMA接入机制:技术原理与实现细节
  • 流水线用到的Dockerfile和构建脚本build.sh
  • Python电影票房预测模型研究——贝叶斯岭回归Ridge、决策树、Adaboost、KNN分析猫眼豆瓣数据
  • MYSQL---存储过程
  • 【轨物方案】“无人值守”光伏电站智能运维解决方案,赋能绿色能源高效运营
  • 正则表达式 —— 贪婪与非贪婪
  • 汽车盲点检测系统的网络安全分析和设计
  • 【Linux学习】正则表达式学习记录
  • 【鸿蒙开发001】上下翻页-翻书效果实现【可复用】
  • 写一个天气查询Mcp Server
  • 2025年9月计算机二级C++语言程序设计——选择题打卡Day8
  • urdf文件可以使用的一些工具(urdf检查和可视化)
  • 【leetcode】100.相同的树
  • Elasticsearch核心配置详解与优化
  • SAP 怎么知道物料的自动过账流程
  • DFT、CHORD
  • “上门经济”的胜利:深度解析家政O2O如何用“用户体验”重塑传统行业
  • 【双指针- LeetCode】15.三数之和
  • CPLD和FPGA的区别
  • LeetCode 560. 和为 K 的子数组
  • Linux827 测试
  • Linux MDIO 深入分析
  • 电子电气架构 --- 软件项目复杂性的驾驭思路
  • [Sync_ai_vid] UNet模型 | 音频特征提取器(Whisper)
  • FPGA实现Aurora 64B66B图像视频传输,基于GTY高速收发器,提供2套工程源码和技术支持