Python模块
# 模块在开发过程中,一个文件里代码越长就越不容易维护。
为了编写易于维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少,很多编程语言都采用这种组织代码的方式。
在Python中,**一个.py文件就称之为一个模块(Module)**。
## 使用模块的几个好处
- 大大提高了代码的可维护性。
- 编写代码不必从零开始。当一个模块编写完毕,就可以被其他模块引用。
- 避免函数名和变量名冲突。相同名字的函数和变量完全可以分别存在不同的模块中。 但还是要注意**变量名尽量不要与BIF(built-in functions,内建函数)名字冲突**。
---
<br>
## 模块名和包名
由于不同的人编写的模块名可能会相同,为了**避免模块名冲突**,Python又引入了**按目录来组织模块**的方法,称为**包(Package)**。

假设图中abc和xyz两个模块的名字和外面其他模块名字冲突了,我们可以通过包来组织模块,避免冲突。 只要**顶层包名(也即这里的mycompany文件夹)不同即可**。
此时abc的**模块名**变为 `mycompany.abc`, xyz的**模块名**变为 `mycompany.xyz`。
**Notice**:
- **注意区分模块和模块名**!两者不一定相同!(比如上面的模块 `abc` 和它的模块名 `mycompany.abc`)
- **每个包目录下都必须有一个 `__init__.py` 文件**,否则Python就不会把这个文件夹当作一个包。`__init__.py`可以是空文件,也可以有Python代码,它本身就是一个模块,并且**它的模块名就是包名**(这里是 `mycompany`)。

**包结构可以是多级的**。比方说这里www.py的**模块名**就是 `mycompany.web.www` 。 两个utils.py的模块名分别是 `mycompany.utils` 和 `mycompany.web.utils`,它们不会冲突 。 `mycompany.web` 这个模块名对应的就是web目录下的 `__init__.py` 模块。
**Notice**:
自己创建的模块的模块名**不要和Python自带的模块的模块名冲突**! 比如系统自带sys模块,自己的模块就不要命名sys.py,否则无法会无法正确import自带的sys模块。
---
<br>
## 使用模块
Python本身就内置了很多模块可以直接import使用。 下面我们自己编写一个Hello模块作为例子:
```python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'This is the docstring(document comment) of this module '
__author__ = 'Lincoln Deng'
import sys
def test():
args = sys.argv
if len(args)==1:
print('No argument is passed.')
elif len(args)==2:
print('Hello, %s!' % args)
else:
print('Too many arguments!')
if __name__=='__main__':
test()
```
### 例子解析
#### Python模块的标准文件模板
文件的第1行和第2行是**标准注释**:
- 第1行注释是用于**声明使用什么程序来执行这个脚本**,它可以**使得这个脚本能在Unix/Linux/Mac上直接运行**(也即**可以使用 `./Hello.py` 的形式执行**而不是 `python Hello.py` 的形式)。如果系统装了多个版本的python,`#!/usr/bin/env python3` 会保证调用环境变量 `$PATH` 中的第一个叫python3的程序来执行脚本。又因为在一些系统中python能被重定向到python3,也即默认使用python3,所以直接用 `#!/usr/bin/env python` 也是可以的。还有一种写法是 `#!/usr/bin/python`,这样写就是指定一个路径,兼容性不如使用env的写法好。注意,如果我们使用 `python Hello.py` 或者 `python3 Hello.py` 的方式**直接指定解释器来执行的话,这句注释就没用了**。另外,在Windows下**这句注释也是被忽略的**,但**为了代码的兼容性最好还是写上**。
- 第2行注释表示(解释器和编辑器)**应使用UTF-8编码来读取这个.py脚本文件中的代码文本**。**在Python2中,默认源代码编码方式为ASCII**,要用到中文就得采用很别扭的escape写法,直接写中文会出错。后来有了(https://www.python.org/dev/peps/pep-0263/),规定了**显式声明代码文本编码**的方法。一般有三种格式,包括:能被大部分编辑器识别的 `# -*- coding: utf-8 -*-`,最简单的 `# coding=utf-8` 以及vim的 `# vim: set fileencoding=utf-8` (其实还可以写成别的方式,主要看编辑器怎样正则匹配这一行)。当然这里的utf-8可以换为其他编码。注意**编码声明必须放在代码文件的第一行或第二行**。另外,这里只是**声明读取时采用的编码方式,保存代码时用什么方式要自己设置编辑器**。实测由于**Python3默认用utf-8编码**,所以只要我们正确使用utf-8编码保存代码文件,那么读取时不声明也没关系。当然,**为了代码的兼容性最好还是写上**。
文件的第4行是一个字符串,表示模块的文档注释,**任何模块的第一个字符串都被视为模块的文档注释;**
文件的第6行使用 `__author__` 变量记录模块作者的名字。
以上就是**Python模块的标准文件模板**,在Windows下使用Python3时,其实不写也没关系,但养成良好的书写习惯更好。
#### 正式代码部分
1) **导入sys模块**
如果想使用Python内置的sys模块,就要先导入该模块: `import sys`。 导入之后,相当于**创建了一个变量`sys`,该变量指向sys模块,通过这个变量可以访问sys模块的全部功能**。
2) **使用argv变量获取参数列表**
**sys模块有一个 `argv` 变量**,这个变量属于list类型,存储着**命令行的所有参数**(即我们在命令行执行该脚本时使用的参数)。`argv` 列表中至少包含一个元素,因为**第一个参数永远是该脚本文件的名称**,例如:
在命令行执行 `python3 Hello.py` 获得的 `sys.argv` 就是 `['Hello.py']`;
在命令行执行 `python3 Hello.py Michael` 获得的 `sys.argv` 就是 `['Hello.py', 'Michael]`。 要获取字符串 `'Michael'` 只需要调用 `sys.argv`。
3) **条件判断**
```python
if __name__=='__main__':
test()
```
在Hello模块中我们定义了一个test函数,这个条件判断的意思就是,如果程序执行到这里,`__name__` 变量的值是 `'__main__'` 的话就执行 `test` 函数。
其中 `__name__` 变量是个特殊变量,当我们**在命令行执行**Hello.py时,Python解释器就会把 `__name__` 变量赋值为 `__main__`。
但**如果在别的文件中导入模块,`__name__` 的值就是模块的名字**而非 `__main__`,此时if判断结果为 `False`。 借助这个特性,我们可以**在if判断中编写一些额外的代码**,用于测试模块的功能,而在使用(导入)模块时,if判断里的代码不会被执行。
#### 在命令行下执行
保存代码文件后,打开命令行,先把路径切换到保存 `Hello.py` 的目录,然后执行:
```
C:\Users\Administrator\Desktop>python Hello.py
No argument is passed.
C:\Users\Administrator\Desktop>python Hello.py Lincoln
Hello, Lincoln!
```
#### 在交互环境下执行
比方说使用IDLE或者在命令行中输入python进入:
```python
C:\Users\Administrator\Desktop>python
Python 3.5.1 |Anaconda 4.0.0 (64-bit)| (default, Feb 16 2016, 09:49:46) on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import Hello
>>> Hello.__name__
'Hello'
>>> Hello.test()
No argument is passed.
```
导入Hello模块时,它的 `__name__` 变量会被赋值为模块名 `Hello`,而不是 `__main__`,所以if判断为 `False`,if判断里的代码不会被执行。如果要使用 `test` 函数,就要通过 `模块名.函数名` 的方式进行调用,使用模块内的其他变量/函数同理。
---
### 命名规范和作用域
在模块中我们会定义很多函数和变量,有些是希望给别人用的,有些则希望仅仅在模块内部使用。
#### 公开(public)的变量和函数
命名格式如: `abc`,`x123`,`PI`, 如果是有特殊用途则**在名称前后各加上两个下划线**,如:`__author__`,`__name__`。
```python
>>> Hello.__doc__
'This is the docstring(document comment) of this module '
>>> Hello.__name__
'Hello'
>>> Hello.__author__
'Lincoln Deng'
>>> Hello.__file__
'C:\\Users\\Administrator\\Desktop\\Hello.py'
```
可以看到 `__doc__` 变量返回了我们前面模块例子的代码中第一个字符串,也即**文档注释(DocString)**。什么是文档注释呢?其实就是一个**模块/类/函数/方法的定义中第一个声明的字符串**,使用这些对象的 `.doc` 属性即可访问。关于文档注释的书写标准可以查看(https://www.python.org/dev/peps/pep-0257/)。
#### 私有(private)的变量和函数
命名格式是在**名称前加一个或两个下划线**,如 `_xxx` 和 `__xxx`。这样的函数或变量**不应该被外部直接引用(即通过 `模块名.变量名` 的方式调用)**。比方说下面定义的 `_private_1` 函数和 `_private_2` 函数,我们不希望使用这个模块的人调用它们:
```python
def _private_1(name):
return 'Hello, %s' % name
def _private_2(name):
return 'Hi, %s' % name
def greeting(name):
if len(name) > 3:
return _private_1(name)
else:
return _private_2(name)
```
虽然不希望用户调用私有函数,但我们**可以暴露给用户一个接口(公开的函数)**,也即这里的 `greeting` 函数,把调用两个私有函数的代码和逻辑封装在里面,用户直接调用 `greeting` 函数,然后让 `greeting` 函数决定怎样调用私有函数。
当用户使用模块时不需要关心私有的变量和函数,直接使用公开的变量和函数就可以了。 这是一种常用的**代码封装和抽象**的方法。
**注意:**
这里说私有函数和变量 **不应该被直接引用**,而不是 **不能被直接引用**。 因为Python没有方法可以限制用户调用私有函数和变量(没有),所以这样命名**只是一种约定的编程习惯**,使用者怎么做就要看他自己怎么决定了。
良好的习惯是**外部不需要引用的函数全部定义成private,只有外部需要引用的函数才定义为public**。
---
<br>
## 安装第三方库
在Python中,安装第三方库(从而使用第三方库中提供的第三方模块),可以通过**包管理工具pip**(也有其他的包管理工具)完成。
安装Python时选择了安装pip的话,就可以直接在命令行中使用pip工具了。
```
pip install Pillow
```
在命令行键入 `pip install 第三方库的库名` 后, pip就会自动帮用户下载并安装第三方库。


这里安装的是**Python Imaging Library**这个第三方库,是一个Python下非常强大的图像处理工具库。 因为PIL只支持到Python2.7,所以这里用的是基于PIL开发的支持Python3的**Pillow**。
安装完成后打开 `F:\Python35\Lib\site-packages` 文件夹(具体路径看安装Python的位置而定)就会发现多了两个文件夹,一个是 `Pillow-3.1.1.dist-info`, 另一个是 `PIL`。 前者包含该库的一些基本信息,后者就是我们需要用到的包了,里面是有 `__init__.py` 文件的。
安装好的包我们可以在文件中直接用 `from 包名 import 模块名` 来导入要使用的模块,而不需要先转到模块所在的目录下再导入,也不需要把模块复制到我们的工程文件夹中。
举一个使用Pillow包中利用Image模块生成图片缩略图的例子:
```python
>>> from PIL import Image
>>> im = Image.open('C:/Users/Administrator/Desktop/test.png') # 打开指定路径下的一张照片
>>> print(im.format, im.size, im.mode) # 打印照片的文件格式&尺寸&颜色模式
PNG (400, 300) RGB
>>> im.thumbnail((200, 100)) # 创建缩略图
>>> im.save('thumb.jpg', 'JPEG') # 保存缩略图
>>> im.show()# 查看图片
```
其他常用的第三方库还有MySQL的驱动:mysql-connector-python,用于科学计算的NumPy库:numpy,用于生成文本的模板工具Jinja2,等等。
---
<br>
## 模块搜索路径
在Python中导入模块时,Python解释器会从指定好的路径中进行搜索。我们可以使用sys模块的变量 `path` 来查看模块的搜索路径,导入模块时会从这些路径中查找.py文件:
```python
C:\Users\Administrator>python
Python 3.5.1 |Anaconda 4.0.0 (64-bit)| (default, Feb 16 2016, 09:49:46) on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.path
['', 'F:\\Anaconda3\\python35.zip', 'F:\\Anaconda3\\DLLs', 'F:\\Anaconda3\\lib', 'F:\\Anaconda3', 'F:\\Anaconda3\\lib\\site-packages',
'F:\\Anaconda3\\lib\\site-packages\\Sphinx-1.3.5-py3.5.egg',
'F:\\Anaconda3\\lib\\site-packages\\win32', 'F:\\Anaconda3\\lib\\site-packages\\win32\\lib',
'F:\\Anaconda3\\lib\\site-packages\\Pythonwin', 'F:\\Anaconda3\\lib\\site-packages\\setuptools-20.3-py3.5.egg']
```
sys模块的 `path` 变量是一个列表,它会**在启动Python时被初始化**,初始赋值(按顺序)由三个部分组成,一是**当前目录**(即列表中的空字符串),二是**环境变量 `PYTHONPATH` 中的路径**,三是一些**默认的路径**(包含内置模块和一些通过pip安装的模块)。由于我没有设置环境变量 `PYTHONPATH`,所以上面只有一和三两部分。尝试新建一个名为 `PYTHONPATH` 的环境变量,添加一条路径指向F盘,**重新启动Python程序**,此时就会发现 `path` 变量的初始赋值中多了F盘的路径了:
```python
C:\Users\Administrator>python
Python 3.5.1 |Anaconda 4.0.0 (64-bit)| (default, Feb 16 2016, 09:49:46) on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.path
['', 'F:\\', 'F:\\Anaconda3\\python35.zip', 'F:\\Anaconda3\\DLLs', 'F:\\Anaconda3\\lib', 'F:\\Anaconda3', 'F:\\Anaconda3\\lib\\site-packages',
'F:\\Anaconda3\\lib\\site-packages\\Sphinx-1.3.5-py3.5.egg',
'F:\\Anaconda3\\lib\\site-packages\\win32', 'F:\\Anaconda3\\lib\\site-packages\\win32\\lib',
'F:\\Anaconda3\\lib\\site-packages\\Pythonwin', 'F:\\Anaconda3\\lib\\site-packages\\setuptools-20.3-py3.5.egg']
```
如果需要使用自己编写的模块,可以把它们放到这些目录中。 也可以自己**增加搜索路径**。具体来说分为两种方法:
- 方法一:**直接使用 `apeend` 往 `sys.path` 列表添加搜索路径**,这种方法**只在该次运行时有效**,重启Python交互环境后会恢复原来的路径:
```python
>>> import sys
>>> sys.path.append('C:/Users/Administrator/Desktop')
```
- 方法二:**配置环境变量PYTHONPATH**,只需要增加自己的搜索路径,默认的路径是不会被覆盖掉的,使用这种方法就**不需要每次都修改 `sys.path` **了。
关于 `sys.path` 的详情可以查看[官方文档](https://docs.python.org/3/library/sys.html#sys.path)。
---
<br>
## 文件搜索路径
这一节与模块无关,但是觉得有必要区分好**文件搜索路径**和**模块搜索路径**! 文件搜索路径是当前工作目录,如果我们不指定路径,直接使用文件名访问文件的时候,Python会从当前路径中进行查找;模块搜索路径则是像上一节提及的那样,指 `sys.path` 列表中包含的路径。
要获取当前工作路径可以使用os模块的 `getcwd` 函数,也即**get current work directory**:
```python
>>> import os
>>> os.getcwd()
'C:\\Users\\Administrator\\Desktop'
```
要调用的文件如果放在当前工作路径上就可以直接用 `文件名.文件格式` 指定,比方说我在桌面放了一张 `test.jpg`,那么访问时直接用 `im = Image.open('test.jpg')` 就可以打开了,无须使用完整的路径,也即 `im = Image.open('C:/Users/Administrator/Desktop/test.jpg')`。
注意Python中**路径字符串里斜杠的使用**, 如果**使用反斜杠划分就必须转义**,也即写作双反斜杠 `\\`;如果**使用左斜杠 `/` 则不需要进行转义**。Python默认路径字符串都使用双反斜杠。
如果文件不在当前工作路径,那么我们写路径时可以有两种写法:
- 使用**绝对路径**
```python
im = Image.open('C:/Users/Administrator/Desktop/test.jpg')
```
这里使用了左斜杠,所以不需要转义。
- 使用**相对路径**
```python
im = Image.open('./test.jpg')
```
相对路径即相对当前工作路径而言的路径,这是为了避免路径过长而设计的,可以**用 `.` 符来代替当前工作路径**。
学习!!
页:
[1]