tongsiying

阅读|运动|自律

0%

第18篇:模块和包

学习目标

了解Python中模块和包的概念,掌握相关包和模块的导入和使用方法

模块是一个包含所有你定义的函数和变量的文件,其后缀名是.py。模块可以被别的程序引入,以使用该模块中的函数等功能。这也是使用 python 标准库的方法。

假设你写了一个10000行代码的程序,如果把全部代码写在一个文件里,查询起来将会非常困难。每次出现错误或异常时,不得不快速浏览10000行代码来查找导致问题的那行。为解决这个问题,程序员将大型程序分割成多个包含Python代码的文件,也就是被称为模块(Module)。Python支持在一个模块中使用另一个模块内的代码,也有内置模块,它是Python语言自带的,包含了许多重要的功能。本节将学习模块及其使用方式。

一、模块的概念

模块就是一个工具包,这个工具包(模块)对外提供全局变量、函数、类三种类型的工具,工具包名称(模块名)实则是一个标识符,需要满足标识符命名规范,而模块的本质就是以.py为扩展名的源代码文件。
模块的开发原则也很简单,就是每一个文件应该是可以被导入的,我们可以理解为:

  • 模块中所有没有任何缩进的代码都应该被执行一遍,加载到内存,以供调用者使用;

  • 模块中的测试代码应该保证仅在其内部使用,而被导入到其他文件中应该不被执行;

    这么一看,那么问题来了,我们该如何让测试代码满足以上的执行条件呢?接下来我们需要学习一下python中的一个内置属性__name_,此属性记录着一个字符串,详情如下表:

    分情况讨论 name
    模块为当前执行程序时 __main__(双下划线字符串)
    模块被其他文件导入时 被导入模块名

由此,我们可以好好使用这个属性,将我们的测试代码放入其中,如下:

1
2
3
if __name__ = "__main__":
# 此处写测试代码
pass

这样,我们的测试代码仅在模块为当前执行程序时,被导入将不再执行,大功告成!最后,给出推荐的模块代码格式,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 导入模块
# 定义全局变量
# 定义类
# 定义函数

def main():
"""
此处为测试代码,是否已解惑各位同学们心中那些年的疑惑呀?
"""
pass

if __name__ = "__main__":
"""
仅在模块为当前执行程序时,执行main();
被其他文件导入时,将不再执行main()
"""
main()

模块的作用总结起来有三点:

  • 提高代码重用性

  • 系统命名空间的划分

  • 实现共享服务和数据

模块分为三种:

  • 内置模块

  • 第三方模块

  • 自定义模块

模块导入规范

  • 文件头部导入
  • 内置模块放在最开始
  • 第三方模块放在中间
  • 自定义模块放在最后
  • 不同种类模块之间空一行

二、包的概念

包是一种管理 Python 模块命名空间的形式,采用”点模块名称”。比如一个模块的名称是 A.B, 那么他表示一个包 A中的子模块 B 。就好像使用模块的时候,你不用担心不同模块之间的全局变量相互影响一样,采用点模块名称这种形式也不用担心不同库之间的模块重名的情况。这样不同的作者都可以提供 NumPy 模块,或者是 Python 图形库。不妨假设你想设计一套统一处理声音文件和数据的模块(或者称之为一个”包”)。现存很多种不同的音频文件格式(基本上都是通过后缀名区分的,例如: .wav,:file:.aiff,:file:.au,),所以你需要有一组不断增加的模块,用来在不同的格式之间转换。并且针对这些音频数据,还有很多不同的操作(比如混音,添加回声,增加均衡器功能,创建人造立体声效果),所以你还需要一组怎么也写不完的模块来处理这些操作。这里给出了一种可能的包结构(在分层的文件系统中):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
sound/                          #顶层包
__init__.py #初始化 sound 包
formats/ #文件格式转换子包
__init__.py
wavread.py
wavwrite.py
aiffread.py
aiffwrite.py
auread.py
auwrite.py
...
effects/ #声音效果子包
__init__.py
echo.py
surround.py
reverse.py
...
filters/ #filters 子包
__init__.py
equalizer.py
vocoder.py
karaoke.py
...

在导入一个包的时候,Python 会根据 sys.path 中的目录来寻找这个包中包含的子目录。

目录只有包含一个叫做 __init__.py 的文件才会被认作是一个包,主要是为了避免一些滥俗的名字(比如叫做 string)不小心的影响搜索路径中的有效模块。

最简单的情况,放一个空的 :file:__init__.py就可以了。当然这个文件中也可以包含一些初始化代码或者为(将在后面介绍的) all变量赋值。

用户可以每次只导入一个包里面的特定模块,比如:

1
import sound.effects.echo

这将会导入子模块:sound.effects.echo。 他必须使用全名去访问:

1
sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)

还有一种导入子模块的方法是:

1
from sound.effects import echo

这同样会导入子模块: echo,并且他不需要那些冗长的前缀,所以他可以这样使用:

1
echo.echofilter(input, output, delay=0.7, atten=4)

还有一种变化就是直接导入一个函数或者变量:

1
from sound.effects.echo import echofilter

同样的,这种方法会导入子模块: echo,并且可以直接使用他的 echofilter() 函数:

1
echofilter(input, output, delay=0.7, atten=4)

注意当使用 from package import item 这种形式的时候,对应的 item 既可以是包里面的子模块(子包),或者包里面定义的其他名称,比如函数,类或者变量。

import 语法会首先把 item 当作一个包定义的名称,如果没找到,再试图按照一个模块去导入。如果还没找到,抛出一个 :exc:ImportError 异常。

反之,如果使用形如 import item.subitem.subsubitem 这种导入形式,除了最后一项,都必须是包,而最后一项则可以是模块或者是包,但是不可以是类,函数或者变量的名字。

  • 模块:一个包含所有你定义的函数和变量的文件,其后缀名是 .py ,一个.py文件就是一个模块

  • 包:一个包含 __init__.py模块 的文件夹,一般也会包含其他一些模块和子包

  • 库(lib):库是完成一定功能的代码集合,具体表现可以是包,也可以是一个模块

  • 框架(framework):为解决一个开放性问题而设计的具有一定约束性的支撑结构

  • python内置了一些库,除此之外,还有其他人自己做的一些库,称之为第三方库

  • 一般把第三方库放在…/python3/lib/site_packages中

三、模块和包的导入

使用模块之前,必须先导入(import):意味着要写代码,以便让Python知道从哪获取模块。Python导入模块有以下几种方式:

1
2
3
4
import module
from module.xx.xx import xx 精确导入
from module.xx.xx import xx as rename
from module.xx.xx import * 模糊导入

导入示例如下:

1
2
3
4
5
6
7
import math
from random import randint
import pandas as pd
from tkinter import *

math.pow(2, 3) # 2的3次方
randint(1, 10) # 随机生成1-10的一个整数

导入模块其实就是告诉Python解释器去解释哪个py文件

  • 导入一个py文件,解释器解释该py文件
  • 导入一个包,解释器解释该包下的 __init__.py 文件

模糊导入中的*中的模块是由__all__来定义的,__init__.py的另外一个作用就是定义package中的__all__,用来模糊导入,如__init__.py

__init__.py 控制着包的导入行为。假如 __init__.py 为空,那么仅仅导入包是什么都做不了的。

1
2
3
4
5
6
7
8
from dingxiangyuan import *  # 虽然dingxiangyuan文件夹下有DBHelper模块 但没有定义__all__
DBHelper
Traceback (most recent call last):
File "D:\python37\lib\site-packages\IPython\core\interactiveshell.py", line 3319, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "<ipython-input-3-60abe7946ad7>", line 1, in <module>
DBHelper
NameError: name 'DBHelper' is not defined

定义变量all

1
__all__ = ['DBHelper']

包外调用

1
2
3
from dingxiangyuan import *
DBHelper
Out[3]: <module 'dingxiangyuan.DBHelper' from 'E:\\ZhangYafei\\project\\丁香园\\dingxiangyuan\\dingxiangyuan\\DBHelper.py'>

从上边的例子可以看出,init.py的主要作用是:

  1. Python中package的标识,不能删除
  2. 定义all用来模糊导入
  3. 编写Python代码(不建议在init中写python模块,可以在包中在创建另外的模块来写,尽量保证init.py简单)

那么问题来了,导入模块时是根据那个路径作为基准来进行的呢?即:sys.path

1
2
3
>>> import sys
>>> sys.path
['', 'F:\\python37\\python37.zip', 'F:\\python37\\DLLs', 'F:\\python37\\lib', 'F:\\python37', 'C:\\Users\\Administrator\\AppData\\Roaming\\Python\\Python37\\site-packages', 'C:\\Users\\Administrator\\AppData\\Roaming\\Python\\Python37\\site-packages\\win32', 'C:\\Users\\Administrator\\AppData\\Roaming\\Python\\Python37\\site-packages\\win32\\lib', 'C:\\Users\\Administrator\\AppData\\Roaming\\Python\\Python37\\site-packages\\Pythonwin', 'F:\\python37\\lib\\site-packages']

搜索规则:

  1. 搜索当前目录,若模块名存在,则直接导入,否则继续搜索;
  2. 搜索系统目录,若模块名存在,则直接导入,否则调用模块中的方法时,将会抛出异常;

如果sys.path路径列表没有你想要的路径,可以通过 sys.path.append('路径')添加。

例如:将当前文件的目录的上一层目录添加到环境路径中。

1
2
3
4
5
6
import os,sys

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

sys.path.append(BASE_DIR)
print(sys.path)

dir() 函数

内置的函数 dir() 可以找到模块内定义的所有名称。以一个字符串列表的形式返回:

1
2
3
4
>>> import os
>>> dir(os)
['DirEntry', 'F_OK', 'MutableMapping', 'O_APPEND', 'O_BINARY', 'O_CREAT', 'O_EXCL', 'O_NOINHERIT', 'O_RANDOM', 'O_RDONLY', 'O_RDWR', 'O_SEQUENTIAL', 'O_SHORT_LIVED', 'O_TEMPORARY', 'O_TEXT', 'O_TRUNC', 'O_WRONLY', 'P_DETACH', 'P_NOWAIT', 'P_NOWAITO', 'P_OVERLAY', 'P_WAIT', 'PathLike', 'R_OK', 'SEEK_CUR', 'SEEK_END', 'SEEK_SET', 'TMP_MAX', 'W_OK', 'X_OK', '_Environ', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '_check_methods', '_execvpe', '_exists', '_exit', '_fspath', '_get_exports_list', '_putenv', '_unsetenv', '_wrap_close', 'abc', 'abort', 'access', 'altsep', 'chdir', 'chmod', 'close', 'closerange', 'cpu_count', 'curdir', 'defpath', 'device_encoding', 'devnull', 'dup', 'dup2', 'environ', 'error', 'execl', 'execle', 'execlp', 'execlpe', 'execv', 'execve', 'execvp', 'execvpe', 'extsep', 'fdopen', 'fsdecode', 'fsencode', 'fspath', 'fstat', 'fsync', 'ftruncate', 'get_exec_path', 'get_handle_inheritable', 'get_inheritable', 'get_terminal_size', 'getcwd', 'getcwdb', 'getenv', 'getlogin', 'getpid', 'getppid', 'isatty', 'kill', 'linesep', 'link', 'listdir', 'lseek', 'lstat', 'makedirs', 'mkdir', 'name', 'open', 'pardir', 'path', 'pathsep', 'pipe', 'popen', 'putenv', 'read', 'readlink', 'remove', 'removedirs', 'rename', 'renames', 'replace', 'rmdir', 'scandir', 'sep', 'set_handle_inheritable', 'set_inheritable', 'spawnl', 'spawnle', 'spawnv', 'spawnve', 'st', 'startfile', 'stat', 'stat_result', 'statvfs_result', 'strerror', 'supports_bytes_environ', 'supports_dir_fd', 'supports_effective_ids', 'supports_fd', 'supports_follow_symlinks', 'symlink', 'sys', 'system', 'terminal_size', 'times', 'times_result', 'truncate', 'umask', 'uname_result', 'unlink', 'urandom', 'utime', 'waitpid', 'walk', 'write']
>>>

如果没有给定参数,那么 dir() 函数会罗列出当前定义的所有名称:

1
2
3
4
5
6
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'os', 'randint', 'sys']
>>> del os
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'randint', 'sys']
>>>

四、第三方模块

安装第三方库实质上是下载并使用别人写的代码。常见的第三方库的格式:源码(Source)(压缩包,需先解压,得到包含setup.py文件夹);egg(本质是压缩包);whl(本质也是压缩文件)。

1. 下载和安装

  • 源码安装(本地安装):手动下载,再安装到本地;

    (1)到对应库托管网站下载所需要的文件,https://pypi.org/或https://www.lfd.uci.edu/~gohlke/pythonlibs/等

    (2)下载的文件要对应自己python的版本,还要选择32位还是64位安装的python的系统

    (3)打开命令行(win+R cmd):切换到下载的文件所在的目录 cd /d

    (4)对于带setup.py的文件(源码文件),输入命令:python setup.py install。注意:若没有setuptools包,需手动下载安装,再利用setuptools安装其他使用setuptools打包的包

    (5)对于 .egg文件使用easy_stall安装,输入命令:easy_install xxx.egg(完整文件名称)

    (6)对于whl文件可以使用easy_stall如上安装,也可以使用pip安装,输入命令:pip install xxx.whl(完整文件名称)

  • 包管理安装(远程安装):通过命令使自动化的为用户安装管理包和模块。安装命令如下:

    1
    yum(linux),pip,apt-get(linux)
  • 使用国内源安装

    (1)默认pip是使用Python官方的源,但是由于国外官方源经常被墙,导致不可用,我们可以使用国内的python镜像源,从而解决Python安装不上库的烦恼。国内有很多源,比如:

    1
    2
    3
    4
    5
    1)阿里云 http://mirrors.aliyun.com/pypi/simple/
    2)豆瓣http://pypi.douban.com/simple/
    3)清华大学 https://pypi.tuna.tsinghua.edu.cn/simple/
    4)中国科学技术大学 http://pypi.mirrors.ustc.edu.cn/simple/
    5)华中科技大学http://pypi.hustunique.com/

    (2)临时使用

    1
    pip install -i https://pypi.tuna.tsinghua.edu.cn/simple 模块名

    (3)永久修改,一劳永逸

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Linux下,修改 ~/.pip/pip.conf (没有就创建一个), 修改 index-url至tuna,内容如下:
    [global]
    index-url = http://mirrors.aliyun.com/pypi/simple/
    [install]
    trusted-host=mirrors.aliyun.com

    windows下,直接在user目录中创建一个pip目录,如:C:\Users\xx\pip,新建文件pip.ini,内容如下:
    [global]
    index-url = http://mirrors.aliyun.com/pypi/simple/
    [install]
    trusted-host=mirrors.aliyun.com

2. 卸载

1
pip uninstall 模块名

3. 更新pip

若提示更新pip

1
2
WARNING: You are using pip version 20.2.3; however, version 20.2.4 is available.
You should consider upgrading via the 'f:\python37\python.exe -m pip install --upgrade pip' command.

按提示输入 python -m pip install –upgrade pip,还是报错

1
2
3
ValueError: Unable to find resource t64.exe in package pip._vendor.distlib
WARNING: You are using pip version 20.2.3; however, version 20.2.4 is available.
You should consider upgrading via the 'F:\python37\python.exe -m pip install --upgrade pip' command

解决方法

1
2
python -m pip uninstall pip setuptools
pip3 install --upgrade pip

五、内置模块

官网网址:https://docs.python.org/3/py-modindex.html

关于内置模块,也称标准库模块,我们将再另一章节讲解。

赞赏一下吧~