第8章 复杂程序组织
当一个应用程序简单时,将程序代码写入一个文件即可。但随着应用程序或项目复杂度增加时,如果将所有代码都写入同一个文件中时,会出现文件过长或过大,即不方便代码浏览,也不方便代码的管理、使用与维护。此时,很自然地会将同一个应用程序或项目内容按照功能或其他标准,分别放入不同的文件。不同的代码文件就是不同的模块,换句话说,每个“.py”文件都是一个模块。
以模块方式组织代码能够方便地管理和维护代码,如果项目的复杂度进一步增加,则模块可能也不能胜任了。于是,将项目中不同功能的代码放入不同的文件夹中,它们可以相互引用,这就是包。模块和包都是复杂程序组织的一种方式,一般来说,复杂度较低的使用模块来管理即可,而复杂度较高的还要用到包来管理代码。 8.1 模块
Python中的模块实际上就是包含函数或者类的Python程序,对于一个大型的程序经常将功能细化,将实现不同功能的代码放在不同的程序中实现,在其他的程序中以模块的形式使用细化的功能,这样便于程序的维护和重用。
8.1.1 模块概述
模块是包含函数和其他语句的Python脚本文件,它以“.py”为后缀名,也就是Python程序的后缀名。用作模块的Python程序与其他的程序并没有什么区别。使用模块中的代码,也很简单,那就是通过导入模块,然后使用模块中提供的函数或者数据。在Python中可以使用以下三种方法导入模块或者模块中的函数。
import 模块名 import 模块名as新名字 from 模块名import函数名
其中使用import是将整个模块导入,而使用from则是将模块中某一个函数或者名字导入,而不是整个模块。
(1) 使用import和from导入模块还有一个不同:使用import的导入模块,要使用模块中的函数则必须以模块名加“.”,然后是函数名的形式调用函数;
(2) 而使用from导入模块中的某个函数,则可以直接使用函数名调用,不用在前面加上模块名称。此外,使用from导入时,函数名处可以只用一个“*”来表示导入该模块中所有代码。但要注意导入的模块中不要与此文件中的代码重复。
(3) 使用“import模块名as新名字”用来在导入模块时给模块重新命一个名字,可能是因为防止名称重复,也可能是为了重新命一个简洁的名字,方便书写。导入一个模块时,会创建新的命名空间,就可以使用命名空间来调用其中的代码;同时,还会在新创建的命名空间中执行模块中包含的代码,如果有输出也可以在控制台看到。
8.1.2 自己编写模块 自己编写模块其实和平常写Python程序是相同的,它既可以是一个解决某个问题的独立程序,也可以是几个函数构成。而模块的名称就是代码保存的文件名。
8.1.3 模块位置
编写好的模块只有被Python找到才能被导入。上一节中编写的模块和调用模块的程序位于同一个目录中,因此不需要进行设置就能被Python找到并导入。如果在该目录中新建一个module目录,并且把module_test.py转移到module目录中。再次在Windows的命令窗口中运行a8_2.py,会引发ImportError错误,即找不到要导入的模块。
ImportError错误表示:Python解释器没有找到module_test模块。在导入模块时,Python解释器首先在当前目录中查找要导入的模。如果未找到模块,Python解释器会从sys模块中的path变量指定的目录查找导入模块。如果在以上所有目录中未找到导入的模块,则会引发ImportError错误。
一般来说,Python解释器在运行程序前将当前目录添加到sys.path路径列表中,所以导入模块时首先查找的路径是当前目录下的模块。在Windows系统下,其他的默认模块查找路径为Python的安装目录及几个子目录,如lib、lib\site-packages、dlls等。在linux下默认模块查找路径为/usr/lib、/usr/lib64及其它们的几个几子目录下。
注意 添加模块查找路径时使用绝对路径。
8.1.4
pycache__目录 在上一节的例子中,运行完usemodule.py会发现moudle目录中除了mymodule.py文件以外还多了一个目录__pycache,目录下有一个module_test.cpython-34.pyc文件。其中module_test.cpython-34.pyc就是
Python将module_test.4.py编译成字节码的文件。虽然Python是脚本语言,但Python可以将程序编译成字节码的形式。对于模块,Python总是在第一次调用后将其编译成字节码的形式,以提高程序的启动速度。
Python在导入模块时会查找模块的字节码文件,如果存在则将编译版后的模块的修改时间同模块的修改时间相比较。如果两者的修改时间不相符,那么Python将重新编译模块,以保证两者内容相符。被编译的程序也是可以直接运行的。当然,没有必要去刻意编译Python程序。不过,由于Python是脚本,如果不想将源文件发布,可以将发布编译后的程序,这样可以起到一定的保护源文件的作用。
8.1.5 具有独立运行能力的模块
每个Python程序在运行时都有一个__name__属性(name前后均是两条下画线)。在程序中通过对__name__属性值的判断,可以让程序在作为导入模块和独立运行时都可以正确运行。
在Python中,如果程序作为模块被导入,则其__name__属性被设置为模块名。如果程序独立运行,则其__name__属性被设置为"__main__"。因此可以通过__name__属性来判断程序的运行状态。
如对实例8-2代码进行修改,它既可以独立运行,又可以作为模块被其他程序导入使用,修改后代码如下:
8.2 包
Python中的模块实际上就是包含函数或者类的Python程序。对于一个大型的程序经常要将功能细化,把实现不同功能的代码放在不同的程序中实现,在其他的程序中以模块的形式使用细化的功能,这样便于程序的维护和重用。
8.2.1 包概述
当应用程序或项目具有较多的功能模块时,如果把它们都放在同一个文件夹下,就显得不合理了。这时,可以使用Python中提供的包来管理较多的功能模块。使用包的好处在于可以有效避免名字冲突,便于包的维护管理。
包其实就是一个文件夹或目录,但其中必须包含一个名为“__init__.py”(init的前后均是两条下画线)的文件。“__init__.py”可以是一个空文件,仅用于表示该目录是一个包。此外,包还可以嵌套,即把子包放在某个包中。包可以看作处于同一目录中的模块。在Python中首先使用目录名,然后再使用模块名导入所需要的模块。要导入子包必须依照包顺序(目录顺序)以点分隔使用import进行导入。
例如,对于一个web项目可能的包组织结构如下:
8.2.2 包详解
上一节中所述的包目录中所属的__init__.py文件是一个空文件,只不过是作为包的一个标志。实际上在第一次导入包中的任何部分,就会执行“__init__.py”文件中的代码,其中的变量和函数等也会自动导入。
“__init__.py”文件中也可以包含可执行包的初始化工作的代码和设置“__all__”变量。对于在from中使用“*”通配符导入包内所有名字时,在“__init__.py”中设置“__all__”变量可以保证名字的正确导入。
8.3 Python常用标准库简介
Python语言中内置的标准库包含200左右的包和模块,提供了广泛的功能,涵盖数据处理、压缩和散列、数据持久化、数据库操作、文件及文件系统处理、日志系统、网络通信、Internet协议、数据编码与转换、多进程与多线程等库和模块。除此以外,Python还有大量的第三方库可以使用。本节简单介绍几个常用的标准库,还有一些标准库将会在本书后面章节加以详细介绍。
8.3.1 数学类模块 此类常用的标准库有math、random。
math中有大量的常见数学计算函数,比如三角函数(sin(),cos(),tan())、反三角函数(asin(),acos(),atn())、对数函数(log(),log10(),log2()),还包括数学中的常量,如e、pi(圆周率)。 random中包含了常见的随机数生成函数,如random、randint,还包括一些按概率生成随机数的函数如gauss等。此外还有像shuffle(乱序列表)、choice()(从序列中随机取元素)等随机函数。 在交互式环境下使用random模块示例代码如下:
>>> import random
>>> random.random()
0.9358315255495109
>>> random.randint(0,10)
0
>>> random.randint(0,10)
4
>>> random.choice((1,2,3,4))
1
>>> alst=[1,2,3,4,5,6]
>>> random.shuffle(alst)
>>> alst
[3, 5, 2, 6, 4, 1]
>>>
8.3.2 日期与时间类
>>> import datetime
>>> datetime.datetime.now()
datetime.datetime(2022, 12, 20, 19, 38, 14, 366602)
>>> import time
>>> time.time()
1671536307.426266
>>> t1 = datetime.datetime(2015, 3, 7, 23, 14, 56, 139627)
>>> t1
datetime.datetime(2015, 3, 7, 23, 14, 56, 139627)
>>> t2 = datatime.datatime.now()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'datatime' is not defined
>>> t2 = datetime.datetime.now()
>>> t2
datetime.datetime(2022, 12, 20, 19, 47, 37, 930659)
>>> sub = t2-t1
>>> sub.seconds
73961
>>> t2.year,t2.month,t2.day,t2.hour,t2.minute,t2.second
(2022, 12, 20, 19, 47, 37)
>>>
8.4 小结 本章主要介绍了Python语言中组织复杂程序或代码的基本方法。对于功能单元不多,而且功能单元中的代码少的小型应用,使用模块就可以管理好代码了。而对于功能单元多的中型应用程序或项目,应该用包和模块来组织代码。通过本章学习你应掌握用包和模块来组织代码的方法,并了解如何探查包或模块的结构。 8.5 本章习题
|