python基础(模块和包)

模块
1)模块就是程序
任何Python程序都可作为模块导入。假设你编写了以下所示的程序,并将其保存在文件hello.py中,这个文件的名称(不包括扩展名.py)将成为模块的名称。
# hello.py 
print("Hello, world!") 
文件的存储位置也很重要。这里假设这个文件存储在目录C:\python(Windows)或~/python(UNIX/macOS)中。
要告诉解释器去哪里查找这个模块,可执行如下命令(以Windows目录为例):
>>> import sys 
>>> sys.path.append('C:/python')

这告诉解释器,除了通常将查找的位置外,还应到目录C:\python中去查找这个模块。这样做后,就可以导入这个模块了(它存储在文件C:\python\hello.py中)。
>>> import hello 
Hello, world!

当你导入模块时,可能发现其所在目录中除源代码文件外,还新建了一个名为__pycache__的子目录(在较旧的Python版本中,是扩展名为.pyc的文件)。这个目录包含处理后的文件,Python能够更高效地处理它们。以后再导入这个模块时,如果.py文件未发生变化,Python将导入处理后的文件,否则将重新生成处理后的文件。删除目录__pycache__不会有任何害处,因为必要时会重新创建它。

如你所见,导入这个模块时,执行了其中的代码。但如果再次导入它,什么事情都不会发生。
>>> import hello 
>>> 
这次为何没有执行代码呢?因为模块并不是用来执行操作(如打印文本)的,而是用于定义变量、函数、类等。鉴于定义只需做一次,因此导入模块多次和导入一次的效果相同。

为何只导入一次
在大多数情况下,只导入一次是重要的优化,且在下述特殊情况下显得尤为重要:两个模块彼此导入对方。
在很多情况下,你可能编写两个这样的模块:需要彼此访问对方的函数和类才能正确地发挥作用。例如,你可能创建了两个模块clientdb和billing,分别包含客户数据库和记账系统的代码。客户数据库可能包含对记账系统的调用(如每月自动向客户发送账单),而记账系统可能需要访问客户数据库的功能才能正确地完成记账。
在这里,如果每个模块都可导入多次,就会出现问题。模块clientdb导入billing,而billing又导入clientdb,结果可想而知:最终将形成无穷的导入循环(还记得无穷递归吗)。然而,由于第二次导入时什么都不会发生,这种循环被打破。
如果一定要重新加载模块,可使用模块importlib中的函数reload,它接受一个参数(要重新加载的模块),并返回重新加载的模块。如果在程序运行时修改了模块,并希望这种修改反映到程序中,这将很有用。要重新加载前述简单的模块hello(它只包含一条print语句),可像下面这样做:

>>> import importlib 
>>> hello = importlib.reload(hello) 
Hello, world! 
这里假设hello已导入(一次)。通过将函数reload的结果赋给hello,用重新加载的版本替换了以前的版本。由于打印出了问候语,说明这里确实导入了这个模块。
通过实例化模块bar中的类Foo创建对象x后,如果重新加载模块bar,并不会重新创建x指向的对象,即x依然是(来自旧版bar的)旧版Foo的对象。要让x指向基于重新加载的模块中的Foo创建的对象,需要重新创建它。

 

2)模块是用来下定义的
模块在首次被导入程序时执行。这看似有点用,但用处不大。让模块值得被创建的原因在于它们像类一样,有自己的作用域。这意味着在模块中定义的类和函数以及对其进行赋值的变量都将成为模块的属性。这看似复杂,但实际上非常简单。
1. 在模块中定义函数
假设你编写了一个类似于以下所示的模块,并将其存储在文件hello2.py中。另外,假设你将这个文件放在了Python解释器能够找到的地方(可前面那样使用sys.path)。

像处理模块那样,让程序(这意味着将被执行,而不是用作模块)可用后,可使用Python解释器开关-m来执行它。如果随其他模块一起安装了文件progname.py(请注意扩展名),即导入了progname,命令python -m progname args将使用命令行参数args来执行程序progname。

# hello2.py 
def hello(): 
     print("Hello, world!") 
现在可以像下面这样导入它:
>>> import hello2 
这将执行这个模块,也就是在这个模块的作用域内定义函数hello,因此可像下面这样访问这个函数:
>>> hello2.hello() 
Hello, world! 
在模块的全局作用域内定义的名称都可像上面这样访问。为何要这样做呢?为何不在主程序中定义一切呢?

主要是为了重用代码。通过将代码放在模块中,就可在多个程序中使用它们。这意味着如果你编写了一个出色的客户数据库,并将其放在模块clientdb中,就可在记账时、发送垃圾邮件(但愿你不会这样做)时以及任何需要访问客户数据的程序中使用它。如果没有放在独立的模块中,就需在每个这样的程序中重新编写它。因此,要让代码是可重用的,务必将其模块化!(这也与抽象紧密相关。)

2. 在模块中添加测试代码
模块用于定义函数和类等,但在有些情况下(实际上是经常),添加一些测试代码来检查情况是否符合预期很有用。例如,如果要确认函数hello管用,你可能将模块hello2重写为以下所示的模块hello3。

# hello3.py 
def hello(): 
    print("Hello, world!") 
# 一个测试:
hello()
这看似合理:如果将这个模块作为普通程序运行,将发现它运行正常。然而,如果在另一个程序中将其作为模块导入,以便能够使用函数hello,也将执行测试代码,就像本章的第一个hello模块一样。
>>> import hello3 
Hello, world! 
>>> hello3.hello() 
Hello, world! 
这不是你想要的结果。要避免这种行为,关键是检查模块是作为程序运行还是被导入另一个程序。为此,需要使用变量__name__。
>>> __name__ 
'__main__' 
>>> hello3.__name__ 
'hello3' 
如你所见,在主程序中(包括解释器的交互式提示符),变量__name__的值是'__main__',而在导入的模块中,这个变量被设置为该模块的名称。因此,要让模块中测试代码的行为更合理,可将其放在一条if语句中,如以下所示。

# hello4.py 
def hello(): 
    print("Hello, world!") 
def test(): 
    hello() 
if __name__ == '__main__': test() 

如果将这个模块作为程序运行,将执行函数hello;如果导入它,其行为将像普通模块一样。
>>> import hello4 
>>> hello4.hello() 
Hello, world! 
如你所见,我将测试代码放在了函数test中。原本可以将这些代码直接放在if语句中,但通过将其放在一个独立的测试函数中,可在程序中导入模块并对其进行测试。
>>> hello4.test() 
Hello, world! 

 

让模块可用
在前面的示例中,我修改了sys.path。sys.path包含一个目录(表示为字符串)列表,解释器将在这些目录中查找模块。然而,通常你不想这样做。最理想的情况是,sys.path一开始就包含正确的目录(你的模块所在的目录)。为此有两种办法:将模块放在正确的位置;告诉解释器到哪里去查找。如果要让别人能够轻松地使用你的模块,那就是另外一码事了。Python打包技术一度日益复杂、各自为政,尽管现已被Python Packaging Authority控制并简化,但需要学习的还是有很多。


1. 将模块放在正确的位置
将模块放在正确的位置很容易,只需找出Python解释器到哪里去查找模块,再将文件放在这个地方即可。在你使用的计算机中,如果Python解释器是管理员安装的,而你有没有管理员权限,就可能无法将模块保存到Python使用的目录中。在这种情况下,需要采用随后要介绍的另一种解决方案:告诉解释器去哪里查找。
你可能还记得,可在模块sys的变量path中找到目录列表(即搜索路径)。
>>> import sys, pprint 
>>> pprint.pprint(sys.path) 
['C:\\Python35\\Lib\\idlelib', 
 'C:\\Python35', 
 'C:\\Python35\\DLLs', 
 'C:\\Python35\\lib', 
 'C:\\Python35\\lib\\plat-win', 
 'C:\\Python35\\lib\\lib-tk', 
 'C:\\Python35\\lib\\site-packages']

如果要打印的数据结构太大,一行容纳不下,可使用模块pprint中的函数pprint(而不是普通print语句)。pprint是个卓越的打印函数,能够更妥善地打印输出。

当然,你得到的打印结果可能与这里显示的不完全相同。这里的要点是,每个字符串都表示一个位置,如果要让解释器能够找到模块,可将其放在其中任何一个位置中。虽然放在这里显示的任何一个位置中都可行,但目录site-packages是最佳的选择,因为它就是用来放置模块的。请在你的计算机中查看sys.path,找到目录site-packages,并将以下所示的模块保存到这里,但要使用另一个名称,如another_hello.py。然后,尝试像下面这样做:
>>> import another_hello 
>>> another_hello.hello() 
Hello, world! 
只要模块位于类似于site-packages这样的地方,所有的程序就都能够导入它。

2. 告诉解释器到哪里去查找
将模块放在正确的位置可能不是合适的解决方案,其中的原因很多。

  •  不希望Python解释器的目录中充斥着你编写的模块。
  •  没有必要的权限,无法将文件保存到Python解释器的目录中。
  •  想将模块放在其他地方。

最重要的是,如果将模块放在其他地方,就必须告诉解释器到哪里去查找。前面说过,要告诉解释器到哪里去查找模块,办法之一是直接修改sys.path,但这种做法不常见。标准做法是将模块所在的目录包含在环境变量PYTHONPATH中。
环境变量PYTHONPATH的内容随操作系统而异,但它基本上类似于sys.path,也是一个目录列表。

环境变量
环境变量并不是Python解释器的一部分,而是操作系统的一部分。大致而言,它们类似于Python变量,但是在Python解释器外面设置的。如果你使用的是bash shell(在大多数类UNIX系统、macOS和较新的Windows版本中都有),就可使用如下命令将~/python附加到环境变量PYTHONPATH末尾:
export PYTHONPATH=$PYTHONPATH:~/python 
如果要对所有启动的shell都执行这个命令,可将其添加到主目录中的.bashrc文件中。

除使用环境变量PYTHONPATH外,还可使用路径配置文件。这些文件的扩展名为.pth,位于一些特殊目录中,包含要添加到sys.path中的目录。

 


为组织模块,可将其编组为包(package)。包其实就是另一种模块,但有趣的是它们可包含其他模块。模块存储在扩展名为.py的文件中,而包则是一个目录。要被Python视为包,目录必须包含文件__init__.py。如果像普通模块一样导入包,文件__init__.py的内容就将是包的内容。例如,如果有一个名为constants的包,而文件constants/__init__.py包含语句PI = 3.14,就可以像下面这样做:
import constants 
print(constants.PI) 
要将模块加入包中,只需将模块文件放在包目录中即可。你还可以在包中嵌套其他包。例如,要创建一个名为drawing的包,其中包含模块shapes和colors,需要创建如以下所示的文件和目录(UNIX路径名)。

                                                                         一种简单的包布局
                                          文件/目录                                                          描 述
                                          ~/python/                                            PYTHONPATH中的目录
                                    ~/python/drawing/                                       包目录(包drawing)
                              ~/python/drawing/__init__.py                          包代码(模块drawing)
                               ~/python/drawing/colors.py                                     模块colors
                             ~/python/drawing/shapes.py                                    模块shapes

完成这些准备工作后,下面的语句都是合法的:
import drawing # (1) 导入drawing包
import drawing.colors # (2) 导入drawing包中的模块colors 
from drawing import shapes # (3) 导入模块shapes
执行第1条语句后,便可使用目录drawing中文件__init__.py的内容,但不能使用模块shapes和colors的内容。执行第2条语句后,便可使用模块colors,但只能通过全限定名drawing.colors来使用。执行第3条语句后,便可使用简化名(即shapes)来使用模块shapes。请注意,这些语句只是示例,并不用像这里做的那样,先导入包再导入其中的模块。换而言之,完全可以只使用
第2条语句,第3条语句亦如此。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值