模块

在开发过程中,一个文件里代码越长就越不容易维护。

为了编写易于维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少,很多编程语言都采用这种组织代码的方式。 在Python中,一个.py文件就称之为一个模块(Module)

使用模块的几个好处

  • 大大提高了代码的可维护性。
  • 编写代码不必从零开始。当一个模块编写完毕,就可以被其他模块引用。
  • 避免函数名和变量名冲突。相同名字的函数和变量完全可以分别存在不同的模块中。 但还是要注意变量名尽量不要与BIF(built-in functions,内建函数)名字冲突


模块名和包名

由于不同的人编写的模块名可能会相同,为了避免模块名冲突,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.utilsmycompany.web.utils,它们不会冲突 。 mycompany.web 这个模块名对应的就是web目录下的 __init__.py 模块。

Notice:

自己创建的模块的模块名不要和Python自带的模块的模块名冲突! 比如系统自带sys模块,自己的模块就不要命名sys.py,否则无法会无法正确import自带的sys模块。



使用模块

Python本身就内置了很多模块可以直接import使用。 下面我们自己编写一个Hello模块作为例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/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[1])
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写法,直接写中文会出错。后来有了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模块的全部功能

  1. 使用argv变量获取参数列表

sys模块有一个 argv 变量,这个变量属于list类型,存储着命令行的所有参数(即我们在命令行执行该脚本时使用的参数)。argv 列表中至少包含一个元素,因为第一个参数永远是该脚本文件的名称,例如:

在命令行执行 python3 Hello.py 获得的 sys.argv 就是 ['Hello.py']

在命令行执行 python3 Hello.py Michael 获得的 sys.argv 就是 ['Hello.py', 'Michael]。 要获取字符串 'Michael' 只需要调用 sys.argv[1]

  1. 条件判断
1
2
if __name__=='__main__':
test()

在Hello模块中我们定义了一个test函数,这个条件判断的意思就是,如果程序执行到这里,__name__ 变量的值是 '__main__' 的话就执行 test 函数。

其中 __name__ 变量是个特殊变量,当我们在命令行执行Hello.py时,Python解释器就会把 __name__ 变量赋值为 __main__

如果在别的文件中导入模块,__name__ 的值就是模块的名字而非 __main__,此时if判断结果为 False。 借助这个特性,我们可以在if判断中编写一些额外的代码,用于测试模块的功能,而在使用(导入)模块时,if判断里的代码不会被执行。

在命令行下执行

保存代码文件后,打开命令行,先把路径切换到保存 Hello.py 的目录,然后执行:

1
2
3
4
5
C:\Users\Administrator\Desktop>python Hello.py
No argument is passed.

C:\Users\Administrator\Desktop>python Hello.py Lincoln
Hello, Lincoln!

在交互环境下执行

比方说使用IDLE或者在命令行中输入python进入:

1
2
3
4
5
6
7
8
C:\Users\Administrator\Desktop>python
Python 3.5.1 |Anaconda 4.0.0 (64-bit)| (default, Feb 16 2016, 09:49:46) [MSC v.1900 64 bit (AMD64)] 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)的变量和函数

命名格式如: abcx123PI, 如果是有特殊用途则在名称前后各加上两个下划线,如:__author____name__

1
2
3
4
5
6
7
8
>>> 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 属性即可访问。关于文档注释的书写标准可以查看PEP 0257

私有(private)的变量和函数

命名格式是在名称前加一个或两个下划线,如 _xxx__xxx。这样的函数或变量不应该被外部直接引用(即通过 模块名.变量名 的方式调用)。比方说下面定义的 _private_1 函数和 _private_2 函数,我们不希望使用这个模块的人调用它们:

1
2
3
4
5
6
7
8
9
10
11
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



安装第三方库

在Python中,安装第三方库(从而使用第三方库中提供的第三方模块),可以通过包管理工具pip(也有其他的包管理工具)完成。

安装Python时选择了安装pip的话,就可以直接在命令行中使用pip工具了。

1
pip install Pillow

在命令行键入 pip install 第三方库的库名 后, pip就会自动帮用户下载并安装第三方库。

安装1
安装2

这里安装的是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模块生成图片缩略图的例子:

1
2
3
4
5
6
7
>>> 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,等等。



模块搜索路径

在Python中导入模块时,Python解释器会从指定好的路径中进行搜索。我们可以使用sys模块的变量 path 来查看模块的搜索路径,导入模块时会从这些路径中查找.py文件:

1
2
3
4
5
6
7
8
9
C:\Users\Administrator>python
Python 3.5.1 |Anaconda 4.0.0 (64-bit)| (default, Feb 16 2016, 09:49:46) [MSC v.1900 64 bit (AMD64)] 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盘的路径了:

1
2
3
4
5
6
7
8
9
C:\Users\Administrator>python
Python 3.5.1 |Anaconda 4.0.0 (64-bit)| (default, Feb 16 2016, 09:49:46) [MSC v.1900 64 bit (AMD64)] 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']

如果需要使用自己编写的模块,可以把它们放到这些目录中。 也可以自己增加搜索路径。具体来说分为两种方法:

  • 方法一:直接使用 apeendsys.path 列表添加搜索路径,这种方法只在该次运行时有效,重启Python交互环境后会恢复原来的路径:
1
2
>>> import sys
>>> sys.path.append('C:/Users/Administrator/Desktop')
  • 方法二:配置环境变量PYTHONPATH,只需要增加自己的搜索路径,默认的路径是不会被覆盖掉的,使用这种方法就不需要每次都修改 sys.path 了。

关于 sys.path 的详情可以查看官方文档



文件搜索路径

这一节与模块无关,但是觉得有必要区分好文件搜索路径模块搜索路径! 文件搜索路径是当前工作目录,如果我们不指定路径,直接使用文件名访问文件的时候,Python会从当前路径中进行查找;模块搜索路径则是像上一节提及的那样,指 sys.path 列表中包含的路径。

要获取当前工作路径可以使用os模块的 getcwd 函数,也即get current work directory

1
2
3
>>> 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默认路径字符串都使用双反斜杠。

如果文件不在当前工作路径,那么我们写路径时可以有两种写法:

  • 使用绝对路径
1
im = Image.open('C:/Users/Administrator/Desktop/test.jpg')

这里使用了左斜杠,所以不需要转义。

  • 使用相对路径
1
im = Image.open('./test.jpg')

相对路径即相对当前工作路径而言的路径,这是为了避免路径过长而设计的,可以. 符来代替当前工作路径

作者

ฅ´ω`ฅ

发布于

2017-06-20

更新于

2021-06-08

许可协议


评论