Python 学习 第九篇:模块

模块是把程序代码和数据封装的Python文件,也就是说,每一个以扩展名py结尾的Python源代码文件都是一个模块。每一个模块文件就是一个独立的命名空间,用于封装顶层变量名;在一个模块文件的顶层定义的所有的变量名(函数名也是一个变量名),称作模块的属性。导入模块给予了对模块的全局作用域中的变量名的读取权,也就是说,在模块导入时,模块文件的全局作用域变成了模块内对象的命名空间。

导入一个模块之后,可以使用模块中定义的属性,例如,在模块moduleA中导入一个模块moduleB,那么moduleB就变成了命名空间,在moduleA中可以使用moduleB中的全局变量,引用的格式是:moduleB.attribution_name。

概括来说,Python中的模块拥有三个大的作用:

  • 代码重用:模块用于保存代码,按照需要导入或重新导入模块,以使用模块中的代码。
  • 变量封装:模块把变量名封装起来,模块变成了一个独立的命名空间,模块封装的变量名变成了属性,这就避免了变量名的冲突。
  • 代码共享:把通用的代码组织成模块,分享给他人。

一,Python程序的结构

一般来说,一个Python程序结构是由一个主文件(又称为顶层文件、脚本)和多个模块构成的,其中主文件包含了程序的控制流程,也就是启动之后能够运行整个程序的文件,而模块文件是用于提供工具的库。

在Python中,一个文件导入了一个模块来获得该模块内定义的工具的访问权,因此,通用的Python程序的结构就是:在顶层文件中导入模块,并引用模块中的工具。

例如,在脚本文件main.py中导入functools模块中的reduce()函数,使用该工具函数实现列表元素的加和:

from functools import reduce
a=reduce((lambda x,y:x+y),range(0,5))
print(a) 

二,模块的创建

定义模块,只需要使用文本编辑器,把一些Python代码输入到文本文件中,然后以".py"为扩展名名进行保存,任何此类文件都会被自动认为是Python模块。在模块顶层指定的变量名是模块的属性。注意:模块的文件名必须以扩展名 .py 结尾。

例如,创建一个strtools模块,文件名必须是strtools.py,文件的内容是Python代码,其中append_suffix是模块的属性名,引用该属性的格式是:strtools.append_suffix(arg):

def append_suffix(str):
    return str+'- py'

三,导入模块

导入从本质上来讲,就是为了载入另一个文件,并能够读取该文件的内容。模块的导入是由两个语句来处理:

  • import语句:以一个整体导入一个模块;
  • from语句:从一个模块文件中导入特定的变量名;

import语句和from语句都会加载模块,加载的过程包括搜索、编译和执行模块文件。两者的差别在于:import语句 会读取整个模块,所以必须进行定义后才能读取它的属性;form语句直接获取模块中特定的变量名。

在一个导入语句中的模块名起到两个作用:识别加载的外部文件;把模块名作为命名空间,用于封装模块内的顶层变量名。在导入模块时,模块定义的对象也会在import语句执行时创建,通过module_name.attribution_name来引用模块内定义的变量名。

例如,在主文件中导入strtools模块,并使用append_suffix()函数:

import strtools
mystr=strtools.append_suffix('It is a cat')
print(mystr)

1,import语句

import语句使用一个变量名来引用整个模块对象,因此,必须通过模块名称来获得模块的属性:

import strtools
strool.append_suffix('xx')

2,from语句

from语句把变量名复制到另一个作用域,因此,可以直接在脚本中使用变量名,而不需要通过模块:

from strtools import append_suffix

append_suffix('xx')

由于模块中的变量名可能由很多,可以使用*,取得模块顶层的所有变量名,把模块顶层的变量名都复制到当前的作用域内:

from strtools import *

从技术上来说,import和from * 语句都会使用相同的导入操作,from *  形式只是多加了步骤,把模块中所有的属性名复制到了导入的作用域之内。

3,导入只发生依次

模块会在第一次import或from时载入并执行,并且只在第一次如此。默认情况下,Python只对每个文件做一次导入操作,之后的导入操作操作都只是取出已加载的模块对象。

4,import和from是赋值语句

import和from是可执行的语句,而不是声明语句,也就是说,被导入的模块和变量名,直到它们所对应的import或from语句执行后,才可以使用。

import和from是隐式的赋值语句:

  • import把整个模块对象赋值给一个变量名,模块文件的名称就是变量名;
  • form把一个或多个变量名称赋值给另一个模块中同名的变量名;以from语句复制的变量名会变成对共享对象的引用。

以from语句复制而来的变量名和其来源的文件之间没有联系,为了实际修改另一个文件中的全局变量名,必须使用import语句。

例如,使用from导入变量x,修改变量不会影响模块中x的值,这是因为from语句把模块的对象复制到本地作用域的变量,所以,本地作用域对变量重新赋值,会创建一个新的对象。

from module import x,y

x=1   # change the variable

但是,对可变类型对象的修改,会影响模块的对象的值,这是因为list_names支持原处修改,这跟赋值不同:

from module import list_names

list_names[0]='vic'

5,import和from的对等性

from只是把变量名从一个模块复制到另一个模块,并不会对模块名本身进行赋值。

从概念上来讲,一个像这样的from语句:

from module import name1,name2

与下面这些语句是等效的:

import module
name1=module.name1
name2=module.name2
del module

就像所有的赋值语句,from语句会在导入者中创建新变量,而这些变量初始化时引用了被导入文件中的同名对象。不过,只有同名的变量名(并不是所有的变量名)被赋值出来,而非模块本身,from未指定的变量名都没有复制出来。

三,模块命名空间

从上文可知,模块是变量名的封装。Python会把每一个.py文件看作一个模块,创建模块对象,以包含模块内所复制的所有顶层变量名,这些顶层变量名就是模块对象的属性。

1,模块语句会在首次导入时执行

模块在第一次导入时,Python都会建立空的模块对象,按照文件从头到尾的顺序,逐一执行模块内的语句。

2,顶层的赋值语句会创建为模块属性

在导入时,在模块顶层的赋值语句左侧的变量,成为模块对象的属性,赋值的变量名会存在模块的命名空间内。

3,模块的命名空间能够通过属性__dict__或dir(module)函数获取

在导入时,Python会把建立模块的命名空间的字典,通过模块对象的属性__dict__来读取,也可以通过dir(module)函数来读取。

4,模块是一个独立的作用域

模块内的命名空间是一个独立的作用域,通过点号 (  .  )来引用属性,引用的格式是:object.attr_name。

四,重载模块

从上文可知,导入只发生一次,要想重载模块,需要 调用 reload()函数。

  • 导入(无论是通过import或from语句)只会在模块第一次导入时,加载和执行模块的代码;
  • 之后的导入只会使用已加载的模块对象,不会重新执行模块的代码;
  • imp.reload()函数在不中止Python程序的情况下,强制Python重新加载和执行模块的代码。

 注意,在使用reload()函数重载模块之前,模块一定是已经预先导入了。

一般的用法是:导入一个模块,在文本编辑器中修改其源代码,然后将其重载。

import module
... use module.attributes

... # now, go change the module file

from imp import reload
reload(module)  # get the changes
... use module.attributes

当调用reload()函数时,Python会重新读取模块问价的源代码,重新执行其顶层代码,在适当的地方修改已导入的模块对象,reload()不会删除并重建模块对象。

在调用reload()函数之后,程序中任何引用该模块的对象,自动会受到重载的影响。

1,重载模块会导致模块代码的重新执行

reload()函数会使模块重新执行模块文件的新代码,重新执行模块文件的代码,会覆盖现有的命名空间,但不是把模块对象删除并进行重建。

2,模块文件中顶层的赋值语句会使得变量名重新赋值

在调用reload()函数时,赋值语句会重新执行,进而模块的顶层变量会被重新赋值,这会导致顶层变量引用的对象会重新创建。

例如,模块重载会导致顶层的def语句重新执行,对函数名进行重新赋值,这使得函数对象会被重新创建,函数名引用的对象不是模块之前的版本。

3,重载对import语句的影响

模块重载会影响所有使用import语句读取了模块的脚本,这是因为使用import语句的脚本,需要通过点号运算获取属性,在模块重载后,模块的属性变成了新值。

4,重载对from语句的影响

模块重载只会对以后使用from语句的脚本造成影响,之前使用from语句来读取属性的变量名不会受到重载的影响,其引用的是值依然是重载之前所获取的对象。

这是因为,模块重载会导致模块顶层变量的重新赋值,这使得模块顶层变量引用的对象会重新创建,模块顶层变量引用的是新对象,而在重载之前执行的from语句,变量引用的是旧版本的对象,这个旧版本的对象不会被销毁,但和重载之后的新对象不是同一个对象了。

5,重载不会传递到导入的模块

重载不会影响模块已经导入的模块,例如,如果要重载模块A,并且模块A导入模块B和C,那么重载只适用于A,而不适用于B和C。在重载模块A时,模块B和C由于已经加载了,模块A只会获取已经载入的模块B和C对象。

如果要重载已经导入的模块,那么必须显式重载这些模块:

from imp import reload
# firstly reload imported modules
reload(C)
reload(B)
# then reload the outer module
reload(A)

五,模块的其他主题

在这一节中,我们探索模块的使用标志,模块内变量的前向引用,和点号运算符的特性等。

1,使用模式的标志

每个模块都有个名为__name__的内置属性,Python会自动设置该属性:

  • 如果文件是以主程序文件执行的额,在启动时,__name__就会设置为字符串"__main__"
  • 如果文件被导入,__name__就会设置模块名

 模块可以检测自己的__name__属性,以确定自己是在执行(run)还是导入(import)。

2,命令行参数

在Python中,sys.argv列表包含了命令行参数,它是反映在命令行上录入的单词的一个字符串列表,其中,第一项总是将要运行的脚本的名称。

import sys

3,import语句和from语句的as扩展

import语句使用as扩展,可以为模块设置别名:

from module import attr_long_name as short_name

from语句把从模块导入的变量名,复制给脚本中的不同的变量名,当模块中的变量名和当前作用域的变量名重名时,使用as扩展,可以避免变量名冲突:

import module_long_name as short_name

4,前向引用

当模块收入导入(或重载)时,Python会从头到尾执行语句,也就是说,语句只会引用前面已经定义的变量,这就是变量的前向引用,因此,模块顶层代码的语句顺序是非常重要的。

  • 在导入时,模块文件顶层的代码(不在函数内),在Python运行到时,就会被立即执行,这意味着,语句无法引用文件后面定义的变量名。
  • 位于函数主体内的代码直到函数被调用时才会执行,因为函数内的变量名在函数实际执行前都不会解析,通常可以引用模块文件内任意地方的变量。

一般来说,前向引用只对立即执行的模块顶层代码有影响,函数可以引用任意一个模块顶层的变量名。

5,from是变量名的赋值,而import是引用对象

其实,from语句是一个普通的赋值运算,把模块内的变量名赋值给导入者的作用域内的变量名,实际上就是把变量名赋值给变量名,两个不同作用域内的变量共享对象的引用,也就是说,一个对象的引用位于不同的文件中。

例如,有模块mod_1,有两个属性,变量x和函数printer:

x=1
def printer():print(x)

在文件mod_2中,使用from导入模块mod_1,对变量x进行赋值,会导致变量x引用新的对象,而不是修改mod_1.py中的变量x的值:

from mod_1 import x, printer

x=2
printer()  # print 1

在文件mod_3中,使用import导入整个模块,当使用点号运算符修改模块内的属性时,就会修改mod_1.py中的变量的值,点号运算符把Python定向到了模块内的变量名,对变量进行修改,而不是赋值:

import mod_1 as m

m.x=2
printer()  # print 2

注意:点号运算符是引用变量,并修改变量的值。

6,reload()函数不会影响from导入

因为from语句在执行时会赋值变量名,所以,不会链接到变量名所在的模块。通过from语句导入的变量名就简单地变成了对象的引用。

当重载模块时,模块会重新创建对象,变量名引用新建的对象,然而位于重载之前的from引用的原始对象并不会改变,重载会影响后面的from语句。

from module import x
...

from imp import reload()
reload(module)       #changes module in-place
x                    #still references old object

reload()函数会影响import语句,这是因为,重载不会删除和新建模块对象,也就是说,import语句引用的模块对象不变,但是,模块对象中的属性会被删除重建。当通过点号来引用模块的对象时,object.attr 会引用模块的最新创建的变量。

import module
...

from imp import reload()
reload(module)       #changes module in-place
module.x             #get current x: reflects module reloads

 

参考文档:

作者悦光阴
本文版权归作者和博客园所有,欢迎转载,但未经作者同意,必须保留此段声明,且在文章页面醒目位置显示原文连接,否则保留追究法律责任的权利。
posted @ 2018-12-20 09:55  悦光阴  阅读(1076)  评论(0编辑  收藏  举报