18. Python中的模块与包

2023-11-08

在这里插入图片描述

Hi, 大家好。我是茶桁。

这一段Python之旅怎么样?还算顺利吧?

之前我们都学习了些什么?有基本常识,流程,函数,不同类型的数据以及一些模块对吧?并且还做了一些练习来巩固所学过的内容。

那么今天,我们接着来学习模块。不过今天要学的模块和以往不太一样了,以前我们学习的都是Python内置的一些模块,而今天呢,我们自己来打包模块。

模块

简单点说,当我们定义一个Python文件,其后缀名为.py的时候,那么这个文件就被称为模块。

模块中通常呢会定义一些相似的类、函数等代码内容,提供给别的程序引入使用。那对于应用,之前我们已经用过很多次了对吧?我们曾多次应用系统模块来使用,那这次,我们还是从系统模块开始吧。

系统模块

系统模块实际上就是一个Python的程序脚本,专门提供给我们自己的程序使用。它们是在安装好Python环境时,就已经存在的,需要的时候可以使用 import 导入到程序中使用。比如:

import os, re, time, json, calendar

自定义模块

那知道了系统模块是什么东西,在理解自定义模块就轻松多了对吧?其实就是我们自己创建一个Python脚本,定义一些类或方法,供别的脚本导入后使用。

由于本节课比较特殊,所以课程源码除了18.ipynb这个笔记本文件之外,还有有一个文件夹,路径为./Python/packages/file,然后内部会有多个.py文件。

比如我们定义一个self.py文件如下:

# self.py

# 定义类
class MyException():
    pass

# 定义函数
def func():
    print('我是一个模块中的func函数')

# 定义变量
myStr = 'iloveyou'

然后让我们在笔记本中引用这个文件(模块)以及其他模块,让我们来看看,还记得我们是怎么引入模块的嘛?来,回忆一下:

# 先引入一个系统模块:time
import time
print(f'time:{time.time()}')

---
time:1692005247.144672

我们引入了一个系统模块time,然后执行了一下模块里的time()方法,并把最终结果打印了出来。

既然都已经有例子了,那我们有样学样来试试引入我们自己创建的文件:

import self

---
ModuleNotFoundError: No module named 'self'

报错了,告诉我们并没有self这个模块。这个…

还记得我们刚才说过的文件路径嘛?./Python/packages/file,而我们当前文件18.ipynb是放在Python目录下的,层级关系如下:

- .
- Python/
- ...
- 18.ipynb
- packages/
		| - self.py

也就是说,我们要应用self.py, 需要找对路径才行。那我们将路径加上去:

# 引入自定义模块
import packages.self 

这回执行之后是没报错了,应该没问题了。

那下面呢,让我们来操作一下文件内的类、函数之类的试试:

# 使用模块中定义的类
obj = packages.self.MyException()
print(obj)

---
<packages.self.MyException object at 0x10468cb80>

没毛病,确实获取到了相关类病打印了出来。

可是我们这也太麻烦了,每次使用这个模块不是抖要输入这么长一段吧?packages.self.xxx, 不知道之前的学习中大家有没有注意到一个关键字as,这个我之前课程中都没有特意讲解过,但是在我们引入模块的时候会经常的用到。所以这里顺带讲一下吧,比如,我们在操作文件的时候有如下代码:

with open('./file', 'w') as fp:
  fp.read()

那这个as我们能猜到是什么作用吗?其实,就是讲as前的内容放入as后面的这个变量里,然后将as身后的这个变量改为一个对象而已,在这段代码里,我们打开了文件,并且将其放入了fp这个变量里,变成了一个fp对象。也可以理解为,我们将as之前的内容起了一个别名。

那么我们导入文件的时候可以这么操作吗?我们试试看:

import packages.self as self
obj = self.MyException()
print(obj)

---
<packages.self.MyException object at 0x1046ce2c0>

嗯,看来我们没搞错,确实可以这么用。

那让我们再来试试文件中的那个函数吧,函数内应该是执行了一段打印方法:

self.func()

---
我是一个模块中的func函数

确实正确执行了。这也太顺利了,趁热打铁,让我们再来获取其中的变量:

print(self.myStr)

---
iloveyou

导入模块其实不是仅可导入模块,还能从一个模块中导入类,方法甚至是变量:

from packages.self import func
from packages.self import myStr as str

func()
print(str)

应该能看出这一段代码的含义吧?就是from(从)一个模块中import导入一个对象。

模块中的测试代码

在自定义模块中,通常我们只是去定义类或函数,变量等,并不调用。如果在指定模块中,想要写一些测试代码,在当前模块作为主程序使用时执行,而作为模块被别的程序导入时不执行,那么可以把测试代码写到下面的代码块中:

if __name__ == '__main__':
	  print('这个位置的代码只有当前脚本被直接运行时才会运行。')

那么这个模块再被别的程序调用之后,这段代码中的程序是不会被执行的。因为只有这个模块作为主程序运行时才会运行这段代码。我们来看下面这些操作就明白了:

import packages.self as self
self

---
<module 'packages.self' from '/Users/du/git/AI_Cheats/Python/packages/self.py'>

按道理,我们引入模块之后应该会拿到该模块内的所有方法,可是刚才我们写的打印并没有被执行。现在我们在命令行内直接大概这个.py文件来试试:

在这里插入图片描述

能看到,if里面的print被直接执行了,打印出了里面的字符串。

在这整段代码中,__name__是一个特殊的变量,这个变量在当前脚本作为模块被别的程序导入时__name__的值是当前这个模块的名称,也就是说,我在笔记本中导入的时候__name__就是self, 而我们在if条件中的设定,是只有当前脚本被作为主程序直接由Python解析时才会进入判断,也就是__name__这个变量的值为__main__时。

我们来看看是不是如此,我们在self.py中加上一段代码:

name = __name__
print(f'__name__: {name}')

然后我们直接让self.py在Python解释器中运行:

在这里插入图片描述

现在让我们在笔记本中重新引入一下模块中的变量name,再打印出来看看:

在这里插入图片描述

打印的第一段内容为引入模块的时候,模块内的print(f'__name__: {name}')执行了一次,第二段内容则是在笔记本中输入的方法print(name)。 这样,我们就很直观的看到了__name__在不同位置时存储了不同的值。

我们在写程序的时候要记得,不要想着把所有的方法定义在一个脚本文件内。

那什么是包呢?包并不是模块。你可以将包理解为一个文件夹,这个文件夹里面包含了多个Python文件。

包的结构

'''
package/   # 包(文件夹)
├── __init__.py  # 包中的初始化文件
├── a.py         # 包中的模块
├── b.py
└── ps/   # 子包
  ├── __init__.py
  ├── c.py
  └── d.py
'''

包的使用方法

其实,我们在刚才所讲的内容中,已经给大家演示过了包的使用方法,不知道小伙伴们能不能反应过来到底是哪里?不知道也没有关系,让我们从头来好好的盘一下这件事。

我们之前在当前目录下创建了一个文件夹packages, 里面有我们self.py文件。实际上,这就是一个包了。

让我们将这个包搞的复杂一点,按照上面我们写的结构来增加一些文件,然后我们看看现在的目录结构:
在这里插入图片描述

我们可以看到,除了我们之前设定的文件之外,还有多出来一个文件夹__pycache__以及文件self.cpython-310.pyc, 这个文件夹和文件是当这个包内的文件存在引入关系的时候,自动生成的缓存文件。大家可以不用管。

下面我们来看具体的包使用方法,我们预先在a, b, c, d这四个文件内都写入了一模一样的代码:

def funca():
  	print('a.py')

当然,方法名和打印的内容都和文件同名的。

然后我们回到18.ipynb这个笔记本文件内,开始操作使用:

import packages as pa
pa.a.funca

---
AttributeError: module 'packages' has no attribute 'a'

似乎并不行,我们好像并不能引用包来直接使用。那我们怎么办呢?前面我们介绍过一个引用的方法from ... import,我们在使用包内的模块时,需要这样去引用。

from packages import a, b
a.funca()
b. funcb()

---
a.py
b.py

可以看到,这回我们引用成功了。那我们之前也学到了,在引入模块的时候,也可以直接就引用模块内的方法和变量,模块在包内也可以如此使用:

from packages.a import funca
funca()

---
a.py

那既然我们得到了这种方式来导入模块内的内容,同样的,包内层如果还存在一个包,而我们要使用子包里的模块,也是这样的导入方法:

from packages.ps import c
c.funcc()

---
c.py

看到了,同样能够正常使用。

那如果再过分点,我们要想导入c.py里的函数可以吗?试试就知道了, 再使用.多链接一层:

from packages.ps.d import funcd
funcd()

---
d.pys

呐,完全没问题。

然后我们再反过来看最开始,其实呢,我们的第一种方法直接引用包不是不可以,这需要用到我们这个包内的__init__.py文件。

__init__.py是一个包内的初始化文件,可以说,没有这个文件,这只是一个文件夹,只有有了这个文件,这才是一个包。在初始化的时候,就把包内的模块导入一次,在__init__.py中写下以下代码:

import a

然后我们再回到笔记本文件中直接导入包来使用试试:

import packages
a.funca()

---
a.py

这样就可以了。

好了,那如果这个时候我packages这个包里一大堆的模块,我不想一个个的来导入,有什么办法吗?也是有的,我们需要用到__all__这个参数,在__init__.py中将包内所有的模块名做成一个列表,然后赋值给__all__这个变量,那么我们在引入包内的模块的时候,就可以使用``*`来代表所有文件:

__init__.py文件:

__all__ = ['a', 'b']

然后进行引入:

from packages import *
b.funcb()

---
b.py

这样,我们就一次性导入了packages这个包里的所有文件。

导入方式的分类

之前我们讲的内容中,把导入的方式都过了一遍。到现在这个位置,我们应该总结一下了。

具体的导入方式,我们可以将其分为两个类别,分别是绝对导入相对导入。那两者有什么区别呢?

绝对导入

绝对导入的方式会使用「搜索路径」去查找和导入指定的包或模块,包括以下几种方式:

  • import module 导入模块

  • import package 导入包

  • import package.module导入包.模块

  • from module import func 从模块中导入函数

  • from package import module从包中导入模块

  • from package.module import func 从包.模块中导入函数

关于「搜索路径」,我们先简单的理解一下就是,从当前文件夹中去找,如果找不到,就会去Python的安装环境中去寻找。

相对导入

⚠️ 相对导入智能在非主程序的模块中使用,不需要直接运行的模块文件。比如:

  • from .包名/模块名 import 模块/内容
  • from ..包名/模块名 import 模块/内容

...我们之前已经了解过了,.代表的就是当前这一级,..代表的就是上一级。

举个栗子好理解:假设我们现在去修改一下ps/c.py这个文件,在这个模块中如果需要当前包中的d模块:

from .d import funcd

注意啊,我们这个时候不要在c.py中直接运行funcd()方法,这样会导致报错:

ImportError: attempted relative import with no known parent package

那我们需要怎么运行呢?我们需要讲c.py导入到其他文件中再执行。比如我们进入到笔记本18.ipynb中导入执行。

import packages.ps.c as c
c.funcd()

---
d.py

然后让我们再在c.py中加上一段内容:

from ..a import funca

小伙伴们应该都看出来了,我是在引用c.py的上一级的a.py

让我们再在笔记本中执行一下试试:

import packages.ps.c as c
c.funcd()
c.funca()

---
d.py
a.py

这样,在我们引入了模块c之后,我们同时也拥有了c.py引入的同级和上一级中的d.pya.py

搜索路径

刚才我们简单提到了一下「搜索路径」, 这里我们详细的来展开说一下。

「搜索路径」就是在导入模块或者包的时候,程序查找的路径。主要的搜索路径包含以下三部分:

  • 当前导入模块的程序所在的文件
  1. python的扩展目录中
  2. python解释器指定的其它 第三方模块位置 /lib/sitepackages

当然,如果你像我一样,系统中安装了多个Python版本,并且使用虚拟环境。那么你的「搜索路径」就不一定是在哪里了。那么我们到底该如何查找呢?我们来看一下以下查找方法:

# 在当前脚本中查看包或者模块的搜索路径
import sys
print(sys.path)

---
['/Users/du/git/AI_Cheats/Python', '/Users/du/miniforge3/envs/glm/lib/python310.zip', '/Users/du/miniforge3/envs/glm/lib/python3.10', '/Users/du/miniforge3/envs/glm/lib/python3.10/lib-dynload', '', '/Users/du/miniforge3/envs/glm/lib/python3.10/site-packages']

我们看到找到的搜索路径被以列表的形式呈现出来。当然,我们找到搜索路径后,其实是可以向其中添加一个的。

sys.path.append('/Users/du/AI/GPT')

单入口程序

那什么是单入口程序呢?顾名思义,这种程序就只有一个入口。那单入口程序就是指整个程序都是经过一个主程序文件在运行,其它程序都封装成了包或模块。

单入口文件是作为程序直接被运行的唯一文件,其他都是作为模块或者包,被导入单入口中去执行。打个比方说,我们要去做一个ATM机的程序,我们来实现一个单入口程序。那么可能的情况如下:

ATM/
|-- main.py  # 当前程序的主入口文件,单入口文件,唯一直接运行的文件
|-- package/ # 主要程序模块包
|-- |--- __init__.py  # 包的初始化文件
|-- |--- View.py      # 视图函数模块
|-- |--- Controller.py# 控制器模块
|-- |--- Card.py      # 银行卡模块
|-- |--- User.py      # 用户模块
|-- databases/ # 数据存储文件夹
|-- |-- user.txt
|-- |-- user_id_card.txt

那么这个程序中,main就是程序的主入口文件,会被直接作为主程序运行。所以main.py文件必须使用「绝对导入」的方式。

好,那讲到这里,我们今天的内容也就结束了。不知道小伙伴们理解了多少?

本节课也不太好放练习,那我们这节课就免了。下去之后,大家去拉取我的源码好好的研究一下引入关系,然后讲包、模块的概念好好的理解透。

那小伙伴们,咱们下节课再见。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

18. Python中的模块与包 的相关文章

随机推荐

  • 关于lcm,gcd的一些性质

    两个整数a b 他们的最大公约数为n 最小公倍数为m 则有 a b都能分解为有限个素数的积 12 2 2 3 1 5 0 30 2 1 3 1 5 1 n为a b所有素因子取较小指数的积 n 2 1 3 1 5 0 6 m为a b所有素因子
  • Win平台高精度Sleep实现

    获取时间戳 GetTickCount Windows平台 可通过GetTickCount和GetTickCount64获取时间戳 它们底层实现是一样的 返回值的位宽不同 GetTickCount返回uint32 t 最大值2 32 单位毫秒
  • OpenCV Python 系列教程2 - OpenCV 的 GUI 特性

    图像入门 学习目标 图像的读取 显示 保存 学习三个函数 cv2 imread cv2 imshow cv2 imwrite 使用 Matplotlib 来显示图像 导入常用包 matplotlib notebook import nump
  • C++ 调用Qml方法

    前言 在Qml和QWidget混合开发中 少不了C 与qml的互相调用 之前总结了一下在qml中调用c 的方法 那反过来如何在业务逻辑中直接修改qml呢 qml调用c 总结 QML和QWidget混合开发 初探 添Qml调用C 方法之自定义
  • IMX6学习记录(9)-实现网络连接,rz/sz文件传输

    上面是我的微信和QQ群 欢迎新朋友的加入 现在我已经自己编译过uboot kernel和rootfs了 遇到一个比较麻烦的事情 拷贝文件需要插拔SD卡 考虑的之后开发的便捷性 准备实现rz sz方式的数据传输 1 修改内核配置 把设备树和默
  • 错误处理--CUDA error: device-side assert triggered(很有效)

    embeding中词的总数设小了 在函数 nn Embedding vocab size embedding dimension 中 参数vocab size 为你字典的总词数 如果在训练中有编号 gt len 字典 的数字出现 就会报这个
  • 服务器怎么打开万向系统,全程监控系统解决方案-20210731072231.doc-原创力文档

    全程监控系统解决方案 全程监控系统解决方案 全程监控系统解决方案 呼集老高速公路 全程监控系统解决方案 二零一零年一月 目录一 需求剖析 错误 不决义书签 二 系统设计方案 错误 不决义书签 系统规划 错误 不决义书签 全程无盲点监控和视频
  • Jira插件安装

    一 Jira插件列表 可以将下面免费插件直接下载 然后登陆jira 在 插件管理 gt 上传插件 将下载后的免费插件直接进行上传安装即可 序号 插件名称 功能概要 供应商 资源 100用户报价 1 GreenHopper 敏捷项目管理 At
  • elasticsearch集群正确关闭、重启方式

    问题原因 在elasticsearch集群中 当集群发现某个节点关闭时 将延迟一分钟后 默认 再开始将该节点上的分片复制到集群中的其他节点 这可能涉及很多I O 由于该节点不久将要重新启动 因此该I O是不必要的 您可以通过在关闭节点之前禁
  • 【博客705】chatgpt:编写日志rotate框架

    chatgpt 编写日志rotate框架 场景 我们的网关服务等为了持久化日志以供排查问题 往往将日志输出到文件 此时如果文件太大 可能导致磁盘被写满 此时就需要对日志文件进行rotate 以保存最新的日志 实现 package main
  • Pytorch中计算自己模型的FLOPs

    转自 Pytorch中计算自己模型的FLOPs thop profile 方法 yolov5s 网络模型参数量 计算量统计 墨理学AI CSDN博客 Pytorch 用thop计算pytorch模型的FLOPs 简书 安装thop pip
  • gcc/gdb/gprof/gcov/valgrind使用

    gcc gdb gprof gcov valgrind使用 ning 发表于 2012年10月05日 23 44 Hits 796 Tag all Table of Contents gcc编译带符号 gdb 启动参数 gdb 查看结构体
  • Blender基础:几何节点修改器

    1 几何节点修改器 几何节点修改器Geometry Node Editor 本质上一种自定义修改器 2 节点的添加 添加节点 节点Node 代表一个函数 功能 演示 添加一个节点 几何数据 变换 连接端口 修改节点的参数 几何节点修改器 是
  • ssh端口转发禁用

    配置1 vi etc ssh sshd config 修改内容 AllowTcpForwarding yes GatewayPorts yes 生效 etc rc d init d sshd restart 配置2 vi etc sysct
  • react中使用useMemo和useCallback

    之前学到的memo是用来优化函数组件的重渲染问题 当传入的属性值都没变化时就不会触发组件的重渲染 否则组件就会重渲染 和类组件中的PureComponent组件是类似 useMemo功能是判断组件中的函数逻辑是否重新执行 用来优化性能 im
  • LUA中的and与or

    逻辑运算符认为false和nil是假 false 其他为真 0也是true and的优先级比or高 其它语言中的and表示两者都为真的时候 才返回为真 而只要有一个假 都返回假 lua虽不仅返回假的语义 还返回导致假的值 也就是说 a an
  • webbench 压力测试软件

    1 安装 wget http home tiscali cz cz210552 distfiles webbench 1 5 tar gz tar xzvf webbench 1 5 tar gz cd webbench 1 5 make
  • Ubuntu Linux输入法fcitx方块乱码解决设置

    Ubuntu Linux 10 04自带的输入法不是很好用 linux下的输入法和windows下的比起来还是有很大差距的 相对来说比较好的输入法我看还是fcitx还不 错 不过在Ubuntu下通过 sudo apt get install
  • java 接口返回json数据封装

    前言 首先 采用的是springboot 在controller中使用了 RestController或者 ResponseBody注解 返回的数据本身就是json格式 但是这样的json串在前后端分离使用中并不满足实际的效果 因此需要进行
  • 18. Python中的模块与包

    Hi 大家好 我是茶桁 这一段Python之旅怎么样 还算顺利吧 之前我们都学习了些什么 有基本常识 流程 函数 不同类型的数据以及一些模块对吧 并且还做了一些练习来巩固所学过的内容 那么今天 我们接着来学习模块 不过今天要学的模块和以往不