Python实战指南:手把手教你掌握300个精彩案例
上QQ阅读APP看书,第一时间看更新

2.1 模块

案例39 独立运行模块

导语

在Python命令行中独立运行模块的方法如下:

代码文件的名称即模块名称,但不包含.py扩展名。假设代码文件命名为abc.py,模块名称就是abc,运行该模块的命令为:

如果要把代码文件作为脚本来运行,需要去掉-m参数,并且指定带扩展名的代码文件名。例如,要将上面的abc.py文件作为脚本来运行,应该输入以下命令:

操作流程

步骤1:新建代码文件,文件名为demo.py。

步骤2:在demo.py文件中输入以下代码:

步骤3:保存并关闭demo.py文件。

步骤4:打开命令行终端,输入以下命令,即可运行demo模块。

步骤5:模块代码执行后,会在屏幕上输出以下内容:

注意:模块运行之后,会在代码文件同级目录下创建名为__pycache__的目录,目录下包含扩展名为.pyc的文件。该文件是Python代码被编译后所产生的二进制文件。在其他代码中使用模块时可以提高运行效率。

案例40 导入模块

导语

在使用模块之前,必须将其导入。导入模块需要使用import语句,其语法如下:

as关键字可以为导入的模块分配一个别名。虽然别名是可选的,但当模块的名称比较复杂时,别名的用途就体现出来了。例如,要导入的模块名为demo_settingslist_v1,使用import语句导入时,可以给它分配一个别名ds1。

分配了别名后,访问模块成员时,只需要加上ds1前缀即可,例如:

这类似于导入模块后,再用新的变量去引用模块。

不过,导入模块后再用新的变量去引用模块,会在当前名称空间(Name space)下同时产生两个变量,一个名为demo_settingslist_v1(与模块同名),另一个名为ds1,两个变量都指向同一个对象——demo_settingslist_v1模块。而使用import…as…语句导入模块后,只在当前名称空间下产生名为ds1的变量,并指向demo_settingslist_v1模块。

import语句可以统一在代码文档的开头使用。但这不是必需的,在代码文档的任何地方,当需要用到某个模块时,都可以使用import语句进行导入。

操作流程

步骤1:新建代码文件,将它命名为emails.py,模块名称为emails。

步骤2:在emails模块中输入以下代码:

send_mail和recv_mails是自定义函数,此处仅作为演示,实现代码也比较简单——只调用print函数输出文本信息。

步骤3:再新建一个代码文件,命名为checker.py,模块名称为checker。

步骤4:在checker模块中输入代码:

check_out和check_in也是自定义函数,pass语句表示不执行任何内容的代码。由于函数体内部必须存在代码语句(不能留空白),所以加上pass语句表示此函数不做任何操作。

步骤5:在需要使用emails模块的地方使用import语句进行导入。

步骤6:调用模块成员时,需要加上模块名字emails。

步骤7:下面代码演示使用import语句导入checker模块,并为它分配一个别名ck。

步骤8:分配别名后,通过ck变量就能访问checker模块的成员了。

案例41 使用from…import语句导入模块

导语

在导入模块时,from…import语句的处理逻辑要比import语句稍复杂一些。from…import语句可以从某个模块中导入指定成员。例如,模块Levels中定义了Test类和Shorten函数,如果要从Levels模块中分别导入这两个成员,可以使用以下代码:

还可以加上as关键字,为导入的成员分配别名。

导入后,在代码中,通过t、s两个变量就可以访问Test和Shorten成员了。

from关键字后面还可能使用相对的模块路径。例如,当前模块下存在子模块B,B模块下存在子模块C,那么,from…import语句还可以这样写

如果当前模块存在父级模块F,那么,在当前模块中导入父模块的成员的方法如下:

模块的相对路径一般用于包(Package)的导入。虽然包在本质上是目录,但它可以被视为模块来导入。路径使用点(.)来分隔,只有一个点(.)的表示导入当前包下面的模块;两个点(..)则表示导入当前包的父级目录下的模块;如果是三个点(…)就导入当前包的父级的父级目录下的模块……以此类推。

import关键字后面还可以使用星号(∗),即把某个模块中的所有成员导入。默认的规则是:命名中不以下画线(_)开头的,或者在__all__变量中声明的成员会被导入。也就是说:

· 如果模块中有__all__变量,那么导入该变量中所列出的成员。

· 如果模块中未定义__all__变量,那么名称中不以下画线开头的成员被导入,名称以下画线开头的成员(如_text、__cut等)会被忽略。

假设要将K模块下的所有成员导入,那么可以输入以下代码:

from…import语句在导入时,会把目标模块中的成员名称复制到当前代码的上下文中,因此可以直接访问。不过,复制的仅仅是成员的名称(引用),成员所引用的对象案例是不会被复制的。

操作流程

步骤1:新建代码文件,命名为mod_1.py,对应的模块名称为mod_1。

步骤2:在mod_1模块中定义work函数和MAX_MESSAGES变量。

步骤3:在要使用mod_1模块的代码中通过from…import语句导入work函数和MAX_MESSAGES变量。

步骤4:导入后可直接访问模块成员,不需要添加模块名称。

步骤5:新建代码文件,命名为mod_2.py,即模块名称为mod_2。然后在模块中定义两个类。

步骤6:在需要使用mod_2模块的代码中导入person和address类,并给它们分配别名。

步骤7:随后可以通过分配的别名来访问mod_2模块中的成员。

步骤8:新建代码文件mod_3.py,对应的模块名称为mod_3。

步骤9:在mod_3模块中定义三个变量和三个函数。

步骤10:在需要使用mod_3模块的代码中使用星号(∗)来导入模块中的所有成员。

步骤11:导入mod_3模块的成员后,输出当前名称空间中的所有变量名。

globals函数返回当前代码文档(模块)中所有全局变量,变量列表以字典的形式呈现。其中,key是变量的名称,value是变量所引用的对象。但是,表示名称空间的字典案例是由Python程序动态维护的,在代码的执行过程中字典会被实时更新,会导致for循环无法正常运行。所以调用globals函数返回字典对象后,还要调用字典案例的copy方法来复制出新的字典案例,再从新的字典案例的keys方法中取出所有key的值。此时,新的字典案例不会被实时更新,就不会影响for循环的运行。

上面代码执行后输出的内容如下:

从输出结果中能看到,mod_3模块中的_get_local函数没有被导入,那是因为它的名称是以下画线(_)开头的,Python解析器将其认定为不应公开的成员,所以不会导入。当然,“不应公开”的概念也仅对import∗语句有效,如果明确指定成员名称,还是可以导入的,例如:

案例42 __all__变量的作用

导语

模块中可以包含一个可选的变量——__all__(“all”前后各有两个下画线)。如果模块中未定义__all__变量,那么from…import∗语句会导入目标模块中名称不以下画线开头的成员;如果模块中定义了__all__变量,那么from…import∗语句只导入__all__中列出的成员。

__all__变量是一个字符串序列,例如列表(list)、元组(tuple)等。变量中所罗列的成员名称必须存在于当前名称空间中,这些成员可以是当前模块中定义的,也可以是从其他模块中导入的成员(使用from…import语句)。

__all__变量中所提供的成员列表只适用于from…import∗导入方式,对于其他导入方式无效。

操作流程

步骤1:新建模块demo_mod,然后在模块中定义三个函数。

步骤2:在模块中定义__all__变量,在列表中仅列出get_first和get_last两个函数。

步骤3:使用from…import∗语句导入demo_mod模块中的所有成员。

步骤4:输出当前名称空间中的变量列表。

执行上述代码,得到的结果如下:

从结果中可以发现,尽管push_item函数的名称不是以下画线开头,但它并没有被导入。这是因为demo_mod模块中定义了__all__变量,并指定了只公开get_first与get_last函数,使push_item函数被隐藏了起来。

案例43 以编程方式生成__all__变量

导语

__all__变量可以使用各种迭代类型,例如列表(list)、元组(tuple),一般可以在模块代码的开始位置直接声明,并把模块中要公开的成员(仅对import∗导入方式有效)填充到迭代类型案例中。

不过,在一些需要动态处理的方案中,需要通过编程的方式来生成__all__变量,例如本案例。在本例中,模块中以下画线开头的或者以prv开头的成员都排除在__all__变量之外(即这些成员不会添加到__all__变量中)。

操作流程

步骤1:新建demo模块(代码文件名为demo.py)。

步骤2:在demo模块中定义6个成员。

步骤3:动态产生__all__变量,排除名称中以下画线和prv开头的成员。

上述代码使用“推导”方式生成列表案例。首先,globals函数返回当前模块中所有全局成员名称;for语句从globals函数返回的字典对象中取出每个项的key,再赋值给变量n;接着用if后面的条件分析一下变量n,如果不是以下画线和prv开头,就添加到列表中。

步骤4:在需要使用demo模块的代码中,以import∗方式导入。

步骤5:为了验证一下名称以下画线和prv开头的成员是否被排除,向屏幕打印当前代码上下文中的全局成员。

在获取字典的key列表时排除了以双下画线开头的名称,去除了输出结果中的干扰项。

步骤6:运行代码后,屏幕输出内容如下:

可以看到,demo模块中只有run_task函数和cancel_task函数被导入,因为prv_get_taskid和prv_extentask函数的名称以prv开头,被排除;_min和_max变量以下画线开头,也被排除。

案例44 为模块编写帮助文档

导语

在代码文件中,出现在所有代码之前的第一个字符串表达式会被认为是模块的帮助文档。与类、函数等对象相似,通过模块的__doc__属性可以获取模块的帮助文档。

操作流程

步骤1:创建test模块,在代码文件的开始处使用字符串表达式编写帮助文档,然后定义两个函数。

步骤2:在需要使用test模块的代码中进行导入。

步骤3:打印test模块的帮助文档。

步骤4:执行上述代码后,屏幕输出结果如下:

案例45 特殊的模块名称——__main__

导语

__main__是一个特殊的模块名称,当一个模块(代码文件)作为顶层代码被执行,该模块的名称就是__main__。如果当前模块被导入其他模块中使用,模块名称不会变成__main__。

以下方式执行模块代码都会使其成为顶层代码(xxx.py是模块文件名):

访问模块的__name__属性可以获取模块的名称,如果模块正在作为顶层代码执行,那么,__name__属性的值就是__main__,否则__name__属性返回模块的实际名称。

操作流程

步骤1:创建demo模块,并在代码文件中输入以下代码:

首先,调用print函数输出模块的名称。接着,判断模块的名称是否叫__main__,如果是,说明demo正被作为顶层代码执行。

步骤2:在命令行控制台中输入以下代码,将demo模块以脚本方式运行。

执行后输出:

步骤3:再输入以下命令,让demo模块独立运行。

得到的结果与步骤2相同。

步骤4:把demo模块导入其他模块。

import语句执行后,输出结果如下:

通过这个例子可以发现:以脚本和独立模块的方式运行demo模块时,模块名称都是__main__,也就是说,只要模块被直接运行了,就属于顶层代码。而如果把demo模块导入其他模块中执行时,它的模块名称依旧是demo,import语句使demo模块的代码被其他代码调用,demo模块的代码不在调用层次的顶端,所以模块名称不会变成__main__。

案例46 __file__与__cached__属性

导语

__file__和__cached__是模块对象的特殊属性,有专门的用途。__file__属性可以获取模块的源代码文件的路径,而__cached__属性则用于获取与模块代码对应的二进制文件的路径(即编译后的文件,扩展名为.pyc)。

操作流程

步骤1:新建代码文件,命名为demo.py,模块名称为demo。

步骤2:在demo模块输入一行代码(此代码仅用于演示)。

步骤3:使用import语句在其他代码文件中导入demo模块。

步骤4:调用print函数分别打印demo模块的__file__和__cached__属性的值。

步骤5:执行上述代码后,屏幕输出内容如下:

注意:由于__file__和__cached__属性都是可以修改的,因此在实际编程时,不能随意更改属性的值,修改属性值虽然并不会破坏源文件,但会破坏模块信息的完整性。