首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Python模块和包

Python模块和包

作者头像
zy010101
发布2022-05-05 15:59:31
发布2022-05-05 15:59:31
7030
举报
文章被收录于专栏:程序员程序员

模块和包

模块是提供自包含的变量的包(命名空间)从而将部件组织为系统的一种可行方式。

一个模块文件顶层定义的所有变量在被导入的时候都变成了模块对象的属性。

模块能够提供的核心功能:

  1. 代码重用
  2. 命名空间划分
  3. 共享的服务和数据 我们通常在顶层文件(就像C/C++语言中的mian.c文件一样)中包含主要控制流程,而在模块(类似于C/C++其它的.h和.c文件)中定义工具库来提供细节操作。我们导入了模块,就能获取它的属性,从而使用模块提供的功能。

import如何工作

import的作用是在一个文件中导入模块。这看起来有点像C/C++语言中的#include操作。但是python的导入和C/C++的#include是完全不一样的。在python中,导入并非只是把一个文件文本插入另一个文件这么简单,导入实际是运行时的操作。(在C/C++中#include是将别的源代码内容插入到所指定的位置,就是这么简单,这就是C/C++编译器在预处理阶段对#include进行的操作)

程序在第一次导入指定文件时,会执行三个步骤:

  1. 找到模块文件
  2. 编译成字节码(如果需要的话)
  3. 执行模块的代码来创建其所定义的对象

这三个步骤(这三个步骤简记为:搜索,编译,运行)只在程序执行期间模块第一次被导入时才会进行,在这之后导入相同模块时,会跳过这三个步骤,而只提取内存中已加载的模块对象。实际上,Python把导入的模块存储到一个名为sys.modules的表中,并在每次导入操作开始的时候先检查该表中是否存在本次导入的模块,如果不存在,则执行上面的三个步骤。

找到模块文件

我们都知道导入一个模块的时候,使用的语句如下所示:

代码语言:javascript
复制
import 模块名

例如:import re,没有带上.py后缀,也没有指出路径。路径和后缀是故意省略掉的,因为python使用了标准模块搜索路径来找出import语句所对应的模块文件。

大多数情况下,我们通常不需要配置模块路径。但如果你想在整个目录的边界都能导入用户自定义的模块。那么你就需要知道路径搜索的原理并进行定制。Python在程序启动时配置sys.path,这其实就是模块搜索的路径字符串列表。sys.path包含以下五个方面的内容。

  1. 程序的主目录 主目录就是包含程序的顶层脚本文件的目录。当在交互式命令行下工作时,主目录就是你当前工作的目录。这个目录总是被优先搜索,因此主目录中的文件会覆盖路径上其它目录中具有相同名称的文件,但不会覆盖内建模块。
  2. PYTHONPATH目录(如果设置了的话) 之后,Python会搜索PYTHONPATH(前提是存在这个环境变量)环境变量中罗列出的所有目录。因为Python会优先搜索主目录,所以当你有跨目录的文件时,PYTHONPATH就显得很重要。
  3. 标准库目录 接着,Python会自动搜索标准库目录。
  4. 任何.pth文件中的内容(如果存在的话) 之后,python允许用户把需要的目录添加到模块搜索路径(即.pth文件)中去。这实际上提供了一种替代PYTHONPATH的方案。
  5. 第三方扩展应用的site-packages主目录 最后,python会自动将标准库的site-packages子目录添加到模块搜索路径。通常情况下,这是大多数第三方扩展安装的地方。

sys.path 可以通过打印sys.path来查看这些路径。这个目录名称字符串列表就是Python内部的实际搜索路径:在导入时,Python会自左至右搜索这个列表中的每个目录,并使用第一个能够匹配的文件。

编译成字节码

当遍历模块搜索路径找到符合import语句的源代码文件之后,如果需要的话(python会检查文件最近一次的修改时间和生成的字节码对应的python版本,从而决定是否需要编译),python接下来会将其编译成字节码。

python通过检查文件最后一次修改时间,如果发现字节码文件比源文件旧,或者是由不同版本的python编译的,就会在程序运行时自动重新生成字节代码,否则不会进行编译。

注意:

  1. 如果Python在搜索路径上只发现了字节码文件,而没有源文件,就会直接加载字节码。
  2. 当文件导入时,才会进行编译。因此,通常不会看见程序顶层文件的.pyc字节码文件,除非这个顶层文件也被其它文件导入。只有被导入的文件才会在机器上留下.pyc文件。顶层文件的字节码是在内部使用后就丢弃的,而被导入文件的字节码则保存在文件中从而可以提高之后导入的速度。
  3. 顶层文件通常是设计成直接执行,而不是被导入。

执行模块的代码来创建其所定义的对象

import操作的最后步骤是执行模块的字节码。被导入文件中所有语句会从头到尾依次执行(导入实际上会执行代码),其中遇到的顶层赋值语句都会产生所导入模块的属性。

下面是一个例子,我们创建一个名为test.py的文件,写入如下代码。

代码语言:javascript
复制
print("我被导入了")
num = 3
def func():
    print("Hello Python!")

然后在交互式命令行模式下,导入test模块。程序执行结果如下所示:

代码语言:javascript
复制
>>> import test
我被导入了
>>> test.num
3
>>> test.func()
Hello Python!

可以看到import之后就会打印“我被导入了”,这说明导入实际上会执行代码。接下来使用 object.attr 的方式来使用test模块的变量和函数。

字节码文件

无论是那个版本的Python,总会在你修改源文件之后重新生成新的字节码文件。但是版本差异的处理方式不一样,在3.2之前的版本中使用魔数,在3.2之后保存多个版本的文件名。(这个实际上和Python解释器有关,在Cpython中确实如此。但是在pypy中,无论是3.2之前还是之后,都会将字节码存放在__pycache目录下。)

字节码的生成是完全自动的,这是程序运行过程中的副产品。

模块文件

文件名后缀在import语句中被刻意省略,Python会选择在搜索路径中第一个能够匹配导入名称的文件。事实上,导入语句的本质是外部组件(源文件,字节码,编译扩展包等)暴露的接口。

对于使用者而言,不在乎被导入的文件是什么类型,因为它们使用起来和Python编写的模块文件并无差异。

选择优先级

如果在不同的路径下存在a.py和a.so,那么python会选择第一次搜索到的a.xx文件,但是如果在同一路径下存在a.py和a.so,python也会有一个规则来选择文件,但是这个规则可能会变动。因此,我们的模块名必须要独特,来保持其唯一性。

导入钩子和ZIP文件

实际上,你可以重新定义import操作的行为,使用导入钩子(import hook)。导入钩子能够让你从压缩文件中加载文件,执行解密等操作。意味着你可以导入任何类型的文件。现在的Python标准库路径中就有一个.zip文件。可以打印sys.path来查看。如下:

代码语言:javascript
复制
>>> import sys
>>> sys.path
[..., '/usr/lib/python38.zip', ...]

优化字节码文件

优化字节码文件一般的后缀是.pyo,这种文件比字节码文件可能快上5%左右。因此一般情况下,如果追求速度,那么直接选择使用PyPy,而不是使用优化字节码。比较优化字节码快的速度依旧是非常有限的。

总结

导入(import)会在搜索,编译,运行导入的文件。如果需要配置搜索路径,那么可以通过配置PYTHONPATH来实现。

import操作和模块是Python中程序架构的核心。较大的程序可以拆分为几个文件,在运行时利用导入链接在一起。导入和模块的意义就是为程序提供结构化的设计,让程序将其逻辑分割成一些独立完备的软件组件。一个模块中的程序代码和另一个的程序代码彼此隔离。模块最小化了程序内不同部分之间的名称冲突。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-01-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 模块和包
    • import如何工作
      • 找到模块文件
      • 编译成字节码
      • 执行模块的代码来创建其所定义的对象
      • 字节码文件
    • 模块文件
      • 选择优先级
    • 导入钩子和ZIP文件
    • 优化字节码文件
    • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档