python模块与包揭秘

python模块与包揭秘

简介

模块是最高级别的程序组织单元,它将程序代码和数据封装起来以便重用。类似于c语言中include进来的头文件。在python中,每一个文件就是一个模块,并且模块导入其它模块之后就可以使用导入模块定义的变量名。

为什么引入模块呢?

代码重用

系统命名空间的划分

实现共享服务和数据

import如何工作

在阅读本文之前,想必你一定编写过这样的代码:

我们首先导入了math模块,然后利用math模块中的sqrt函数计算了4的平方根。让我们再细致一点来看待这个问题。

我在之前提到过,其实import也是执行了一个赋值操作,它把我们需要导入的目标模块对象赋值给了对应的变量名,例如上例就是把math模块对象赋值给了math这个变量名,然后math所指向的模块对象中的内容(函数、最外层的变量)都可以认为是math这个对象的属性(方法),所以我们可以用object.attr的形式来访问。

math模块对象所有的属性

写过c语言程序的人都喜欢把python中的import比作c中的,其实这是不太正确的,因为import不只是把一个文件插入到另一个文件,导入时运行时的运算,程序第一次导入指定文件是,会执行三个步骤:

找到模块文件

编译成位码(需要时)

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

上面三个步骤都比较好理解,第三步需要记住,第一次导入模块文件时,模块文件是会运行的,所以如果你的模块文件中又print语句之类的就要注意它们是否影响你的程序了,而关于模块的搜索路径也需要提一下,如果你不知道可能会出一些小bug。

模块文件的搜索路径:

程序主目录

pythonpath目录(python的环境变量)

标准链接库目录

任何.pth文件的内容

pythonpath就是python得一个环境变量,安装了python的朋友应该都配置了的吧:

这里写图片描述

我之前还真遇到一个与搜索路径相关的一个bug,当时自己也是啥都不懂(加上有点脑残),也没学python,我就随便编写了一个文件保存为random.py,然后这个文件中有这么一段代码:

random是一个内建模块,random.choice(a)就是从a列表当中任意取出一个值。本来是很简单的代码,但是就是报错,提示就是random模块没有choice这个方法,我当时纠结了好久,真的是脑残,现在大家应该不会再犯这种错误了吧。

想要知道你的机器上python模块的搜索路径可以查看sys.path

这里写图片描述导入模块时的一些细节

我们都知道导入模块有import与from两个语句,这两个语句的区别有必要再次啰嗦一下。首先我们已经知道导入是一种赋值操作。

import语句我们前面也说了,模块对象被赋值给了一个变量名,然后那个模块中的内容都是通过【模块名.属性】的形式访问的,也就是被导入模块的命名空间与当前文件的命名空间是独立的,没有相互污染。

这里写图片描述

其中hello模块的代码如下:

from与import还是有所差异的,从某种角度来说,这种差异有时候会导致很麻烦的问题。这个差异就体现在,通过from导入的变量名(不应该说是变量名,但是我不知道怎么形容更好),在当前文件中可以直接访问,不需要再通过先前说的【模块名.属性】的形式,例如上面的例子用from语句重写:

这里写图片描述

这就意味着被导入模块中的变量名可能会与当前模块中的变量名冲突(如果使用from … import * 语句尤其危险),这在一些大型项目中需要特别注意。

主要是注意from … import * 语句

实际上from只是在import的基础上多做了一次赋值操作而已,例如下面语句:

等价于

重载模块

模块只会导入一次,如果想要再次导入需要使用imp.reload函数。注意这里的重载不是oop中的重载。reload函数主要是让我们的程序变得更加动态:

只会在第一次导入时,加载和执行该模块代码

之后得导入只会使用已加载得模块对象

reload函数会强制已加载得模块得代码重新载入并重新执行。

reload函数可以使程序更加动态,这个动态性体现在哪里呢?

reload可以只是修改程序的一部分,而无须停止整个程序。不知道大家是否对自己的计算机做过一些配置,该配置需要开机重启后才能生效?这就是非动态的,如果做成动态的,那么我们进行的配置,就会在立即生效(或者隔一定时间)。

我们通过一个例子来更直观的看一下reload的效果:

可以看到hello.py的内容在reload后又执行了一次!

休息一下

前面我们也认识了模块的常见用法及内部机理,是时候看看包了。实际上python代码的目录就称为包,因此导入目录就是导入包。事实上,包导入是把计算机上的目录编程另一个python命名空间,而属性则对应与目录中所包含的子目录或模块文件。

现在我有如下的目录结构:

mask.py内容:

test.py内容:

执行test.py效果如下:

这里写图片描述

通过结果可以看到,我们成功导入了mask.py,而且是通过我们之前没有接触过的方式,其实from dir0.dir1.dir2 import mask的意思就是从from后面的目录中导入import语句后面的模块,这样做的好处就是可以增加确定性,否则当你的工程过大的时候,在不同目录下存在同名文件,那么使用之前的模块导入的方式就会出现问题。

这个问题是怎么产生的呢?例如当我自定义了一个文件叫做string.py,然后我另一个文件中有import string这么一条语句,目的是导入内建模块string,但是却导入了我自己写的string.py,这是由于在python2.7中默认先搜索当前目录,然后再去pythonpath里寻找,所以当程序执行到import string时,先找到了我自己写的string.py。

关于包还有一个比较重要的地方,可能大家从我上面给的例子中也发现了这么一个文件__init__.py,这个文件是把一个目录变成一个python包的关键所在,它里面可以没有任何内容,但是必须得存在!否则在进行导入得时候会发生错误。那这个文件到底是干嘛的呢?

其实从它的名字就可以知道它是用作初始化的,python在首次进行包导入时,都会执行相应的__init__.py,所以如果你在这个文件中赋值了一些变量,那么在包导入之后,这些变量会出现在该包的命名空间中,而且在__init__.py中我们还常定义__all__列表。这个列表中的值指出了执行from ... import *语句时会被导入的属性。例如在目录test下有如下__init__.py:

那么在执行from test import *时,该目录下的x,y,z模块都会被导入。当然也可以不设置__all__,它的作用就是可以自定义哪些文件或变量可以使用from *语句导入(否则默认导入该目录下的所有)。

包相对导入

读者一定知道相对路径与绝对路径吧。这里的相对导入与绝对导入其实就与他们类似。前面我们说的就是绝对导入,直接给出一个绝对的路径(其实也是相对路径,看你怎么理解),现在我们不想那么写,就给出一个相对于当前目录的包路径,这样做就就更加明确了模块位置

其它

前面提到了通过自定义__all__可以确定哪些模块再from *时会被导入,其实还有一种方法,可以避免哪些模块或变量被导入,那就是将他们命名为单下划线开头的名称。

还有一个用的比较广泛的技巧:每个模块都有一个__name__属性,如果一个文件是作为模块被导入的那么__name__的值就是它的文件名,如果一个文件是作为主程序运行的(也就是不是被导入的),那么它的__name__就为__main__

sys.path也就是模块搜索路径是可以被程序动态修改的

import与from语句都有一个as功能,就是给导入的包起一个简短的别名,如:

  • 发表于:
  • 原文链接:https://kuaibao.qq.com/s/20180627G0LGY500?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券