4.14. 模块和包

python里用模块和包组织代码,模块就是一个个以 .py 为后缀的文件,包就是一个个带有 __init__.py 文件的文件夹。

4.14.1. 怎么理解模块

可以把模块理解成一个日记本,不同的日记本里都可以出现6月6号的日记,但是因为分属于不同的日记本,所以引用的时候不会出错,比如我们可以描述为A日记本某日的日记,B日记本某日的日记

4.14.2. 定义、使用模块

新建一个以 .py 结尾的文件,就是新建了一个模块,大家新建一个 a.py 文件,然后输入以下内容:

# coding=utf-8

num = 1

def demo():
    print('im in a.py')

使用模块有如下三种方式,第一种:

from a import *

这种方式从字面理解是从a模块中导入所有内容(*是所有的意思),然后就可以使用模块中的变量和函数了,如新建一个 b.py ,然后输入以下内容

from a import *

print(num)
demo()

看,在b模块使用a模块的变量和函数,就好像在a块中一样,但这种方式存在一个严重问题,但a模块中变量非常多,如果全部导入会污染b模块的命名空间,如,b里也有一个变量叫num,如果导入语句出现在num定义后,则会出现变量被覆盖的情况,所以非常不推荐这种方法。

第二种:

import a

这种方式是非常好的,导入模块对象,然后使用的时候可以通过 点号 取值,即 a.numa.demo 这样子,这样即可以知道这些变量和函数是a模块里的,也不会污染b的命名空间。

第三种:

from a import num, demo

第三种方法看起来和第一种很相似,只是把 * 换成了具体的变量名,虽说原理相似,但这种按需导入的方式,即使变量名有冲突,也可以很快的找出来,使用方式同第一种。

4.14.3. __name__是什么?

经常在模块里看到如下语句:

if __name__ == '__main__':
    xxxx

那么 __name__ 是什么? __main__ 又是什么呢?故名思意, __name 是名字,模块的名字,比如:

import a
print(a.__name__)

大家可以看到输出为 a 也就是模块的名称,但是在a模块里执行 print(__name__) 会是什么呢?结果是 __main__ ,也就是说,当模块被当做运行主体时, __name__ 的值是 __main__ ,但被导入的时候,值为模块名。所以可以用 if __name__ == '__main__':xxx 去判断只在运行该模块运行时执行下面的语句,被导入时不执行。

4.14.4. 引用标准库里的模块

python标准库里有很多现在的模块可以使用,如果 systimedatetime 等等,使用标准库里的模块与我们写的方式是一样的,如:

# coding=utf-8

import sys
import time
import datetime

print(sys.path)
print(time.time())
print(datetime.datetime.now())

标准库里的模块非常之多,覆盖数据结构、文件系统、字符处理、时间日期等等, 大家可以看一下这个目录,https://docs.python.org/2/library/index.html

4.14.5. 什么是包?

上面我们把模块比喻成了日记本,但在我们可以把包比喻成盒子,盒子里可以放日记本,每个盒子都有自己的名字,如 box_abox_b ,包在实现体现就是文件夹,新建一个文件夹,然后在里面放一个名为 __init__.py 的文件(空的也可以,但名字要对),这时这外文件夹也被称为包。包是用来组织模块的,以用来避免模块名重复的问题,同时也可以把功能类似的模块放到一个包里,即可以起到分类的作用。

4.14.6. 怎么使用包

使用包非常简单,就跟模块差不多,只是要加上包名,如 from box_a.a import num ,先是包名,然后是点号,通过点号索引下面的模块。

4.14.7. 嵌套包

包内不仅可以模块,还可以再放入包,思考如下结构:

└── outer (外面的包)
    ├── __init__.py
    ├── a.py
    ├── b.py
    └── inner (里面的包)
        ├── __init__.py
        └── c.py

2 directories, 5 files

怎么导入c模块呢?同样可以使用点号去索引里面的包,如:

from outer.inner import c

4.14.8. import是怎么查找包或模块的呢?

比如说,你有两个包,一个在C盘,一个在D盘,两个包名字一样,那么导入的时候,是导入哪一个呢?答案是可能都导入不了,python是按照 sys.path 里的路径去查找包或模块的,该列表的内容大致如下:

['',
'/usr/local/Cellar/pyenv/1.0.10/versions/2.7.13/bin',
'/usr/local/opt/pyenv/versions/2.7.13/lib/python27.zip',
'/usr/local/opt/pyenv/versions/2.7.13/lib/python2.7',
'/usr/local/opt/pyenv/versions/2.7.13/lib/python2.7/plat-darwin',
'/usr/local/opt/pyenv/versions/2.7.13/lib/python2.7/plat-mac',
'/usr/local/opt/pyenv/versions/2.7.13/lib/python2.7/plat-mac/lib-scriptpackages',
'/usr/local/opt/pyenv/versions/2.7.13/lib/python2.7/lib-tk',
'/usr/local/opt/pyenv/versions/2.7.13/lib/python2.7/lib-old',
'/usr/local/opt/pyenv/versions/2.7.13/lib/python2.7/lib-dynload',
'/usr/local/opt/pyenv/versions/2.7.13/lib/python2.7/site-packages',
'/usr/local/opt/pyenv/versions/2.7.13/lib/python2.7/site-packages/IPython/extensions',
]

大家可能跟我的不太一样,但是类似,在这个列表里,第一个元素是空字符串,后面是一些路径,路径就不用解释了,这个空字符串是代表当前目录,也就是你所在的目录。当在当前目录找不到时,会按着这个列表顺序去下个路径找,以此类推,最后还找不到,就会报错。回到正题,我们怎么运行例如 c:\outer 包呢?

第一种方式

我们看一下,outer包在c盘下,也就是说c盘在查找路径里,我们就可以找到该包,所以在需要引用outer包的脚本里加上 sys.path.append(r'c:/') ,这种修改只是临时性的,脚本执行完后,又会恢复原状。

第二种方式

除了修改查找路径,我们还可以在当前路径上做文章,我们去包路径下执行不就得了?因为此时,当前路径就是包所在的路径了,所以自然可以找到包。