此部分来自于《Python学习手册》第五部分
一、模块(21章)
模块是最高级别的程序组织单元,它将程序代码和数据封装起来以便重用。。模块往往对应于python程序文件。每个文件就是一个模块,并且模块导入其他模块之后就可以使用导入模块定义的变量名。模块可以由两个语句和一个重要的内置函数进行处理:a、import,使客户端(导入者)以一个整体获取一个模块;b、from,允许客户端从一个模块文件中获取特定的变量名;c、imp.reload,在不中止python程序的情况下,提供了一种重新载入模块文件代码的方法。这一章可以学到reload、__name__和__all__属性、封装import、相对导入语法等。因为模块和类实际上就是一个重要的命名空间,这里也会介绍命名空间。
1、模块通过使用自包含的变量的包,也就是命名空间提供了将部件组织为系统的简单的方法。在一个模块文件的顶层定义的所有的变量名都成了被导入的模块对象的属性。导入给予了对模块的全局作用域中的变量名的读取权。也就是说,在模块导入时,模块文件的全局作用域变成了模块对象的命名空间,最后模块将独立的文件连接成一个更大的 程序系统;模块至少有三个用处:a、代码重用;b、系统命名空间的划分;c、实现共享服务和数据。
2、python的程序架构。一般来讲一个程序包括了许多含有python语句的文本文件。程序作为一个主体的、顶层的文件来构造,配合有零个惑多个支持的文件,在python中这些文件称作模块。一个文件导入了一个模块来获取这个模块定义的工具的访问权,这些工具被认作是这个模块的属性(也就是说,附加类似于函数这样的对象上的变量名)。
3、,一个程序是一个模块的系统,它有一个顶层脚本文件以及多个模块文件。
在python中,交叉文件的模块链接在导入语句执行时才会进行解析,实际效果就是,将模块名(简单的认为变量名)赋值给载入的模块对象。事实上,在一个导入语句中的模块名起到两个作用:识别加载的外部文档,但是它也会变成赋值给被载入模块的变量。模块定义的对象也会在执行时创建,就在import执行时,import会一次运行在目标文档中的语句从而建立其中的内容。
4、标准库模块:在本书发布的时候,python的标准链接库的集合体大约有200个模块:与平台部相关的常见程序设计任务:操作系统接口、对象永久保存、文字模式匹配、网络和internet脚本、gui建构等。这些都不是python的组成部分,但是可以在任何安装了标准python的情况下,导入适当的模块来使用。
5、import如何工作:有些c程序设计者喜欢把python的模块导入操作比作c语言的#include,但是不是这样的:在python中,导入并非只是把一个文件文本插入另一个文件而已,导入其实是运行时的运算,程序第一次导入指定文件时,会执行三个步骤:a、找到模块文件;b、编译成位码(需要时);c、执行模块的代码来创建其所定义的对象。这三个步骤只在程序执行时,模块第一次导入时才会执行。在这之后,导入相同模块时,会跳过这三个不再,而只提取内存中已加载的模块对象。从技术上说,python把载入的模块存储到一个名为sys.modules的表中,并在一次导入操作的开始检查该表。如果不存在,则会启动一个三个步骤的过程。
6、接上5,a、搜索,python需要查找到import语句所引用的模块文件。比如只能import b,而不能使用import c:\dir1\b.py,只能列出简单的名称,路径和后缀都需要省略。因为python会使用标准模块搜索路径来找出import语句所对应的模块文件。b、找到源代码文件后,如果必要的话,python接下来会将其编译成字节码,通过检查时间戳,如果字节码文件比源码文件旧,就会自动重新生成字节代码;如果发现.pyc字节码文件没有比.py源文件旧,那么就跳过源代码到字节码的编译步骤。ps:当文件导入时,就会进行编译,所以通常不会看见程序顶层文件的.pyc字节码文件,除非这个文件也被其他文件导入:只有被导入的文件才会在机器上留下.pyc。顶层文件的字节码是在内部使用后就丢弃了的。顶层文件通常是设计成直接执行,而不是被导入的。c、在运行的时候,文件中的def语句会在导入时执行,来创建函数,并将模块内的属性赋值给那些函数。之后,函数就能被程序中这个文件的导入者来调用。
7、模块搜索路径,在大多数情况下,可以依赖模块导入搜索路径的自动特性,完全不需要配置这些路径。不过,如果想在用户间定义目录边界来导入文件,就需要知道搜索路径是如何运行的,并予以调整。概况的说,python的模块搜索路径是这些主要组件组合而成的结果。其中有些进行了预定义,而其中有些可以进行调整来告诉python去哪里搜索。ps:正如前面说的,python已经导入的模块保存在一个内置的sys.modules字典中,以便可以记录哪些已经导入了。实际上,如果想要看看已经导入了哪些模块,可以导入sys并打印list(sys.modules.key())。:a、程序的主目录;b、PYTHONPATH目录(如果已经进行了设置);c、标准链接库目录;d、任何.pth文件的内容(如果存在的话)。这四个组件组合起来就变成了sys.path,它是下一部分详细介绍的目录名称字符串的列表。搜索路径的第一和第三是自动定义的,但是因为python会从头到尾搜索这些组件组合的结果,第二和第四,就可以用于拓展路径,从而包含自己的源代码路径。
8、接7的:a、主目录,当运行一个程序的时候,这个入口是包含程序的顶层脚本文件的目录;当在交互模式下工作时,这一入口就是当前工作的目录。b、PYTHONPATH,python会从左到右搜索PYTHONPATH环境变量设置中罗列出所有目录。简单来说,PYTHONPATH是设置包含python程序文件的目录的列表,这些目录可以是用户定义的或平台特定的目录名,可以把想导入的目录都加进来,而python会使用新的设置来扩展模块搜索的路径。c、标准库目录:python会自动搜索标准库模块安装在机器上的那些目录。因为这些一定会被搜索,通常是不需要添加到PYTHONPATH之中惑包含到路径文件中的;d、.pth文件目录:这里允许用户把有效的目录添加到模块搜索路径中去,也就是在后缀名为.pth的文本文件中一行一行的列出目录。当内含目录名称的文本文件放在适当目录中时,也可以概括的扮演与PYTHONPATH环境变量设置相同的角色。
9、配置搜索路径:搜索路径的PYTHONPATH和路径文件部分允许我们调整导入查找文件的地方。设置环境变量的方法以及存储路径文件的位置,随着每种平台的变化而变化。例如在windows上,可以将PYTHONPATH设置为分号隔开的一串目录:
,
或者创建一个名为
的文本文件,其内容为:
。
10、搜索路径的变动,搜索路径的配置可能随平台以及python版本而异,取决于所使用的平台,附加的目录也可能自动加入模块搜索路径。例如:python可能会把当前的工作目录也加进来(启动程序所在的目录),放在搜索路径PYTHONPATH之后,并在标准库这项之前,在从命令行启动时,当前工作目录和顶层文件的主目录(也就是程序文件所在的目录)不一定相同。因为每次程序执行时,当前工作目录可能都会变,一般来说,不应该依赖这个值进行导入。可以通过查看sys.path来知道python在平台配置的模块搜索路径。
11、接上10:sys.path列表。sys.path是模块搜索的路径,python在程序启动时进行配置,自动将顶级文件的主目录(或者指定当前工作目录的一个空字符串),任何PYTHONPATH目录,已经创建的任何.PTH文件路径的内容,以及标准目录合并。结果是python在每次导入一个新文件的时候查找的目录名的字符串的列表。python有两方面来描述这个列表:a、提供一种方式来确认你所做的搜索路径的设置值:如果在列表中看不到设置值,就需要重新检查你的设置,假设在windows的3.0中PYTHONPATH设置为C;\users并列出了C:\user\mark的一个C:\Python30\mypath.py路径文件。前面的空字符串表示当前路径。并且两个设置都合并进去了(其余部分是标准库目录和文件):【ps:第23章,会有新的相对导入语法的讨论】通过使用sys.path的修改可以手动调整其搜索路径,然而,这种修改只会在脚本存在期间保持而已。PYTHONPATH和.pth文件提供了更持久的路径修改方法。
12、文件名的后缀(.py)是特意从import中省略的。python会选择在搜索路径中第一个符合导入文件名的文件,例如,import b 形式的import叙述可能会加载:1、源代码文件b.py;2、字节码文件b.pyc;3、目录b,包导入(第23章说明);4、编译扩展模块(通常是c、cpp写的),导入时使用动态链接(linux的b.so以及Cygwin和windows的b.dll或b.pyd);5、用C编写的编译好的内置模块,并通过静态链接至Python;6、ZIP文件组件,导入时会自动解压缩;7、内存内映像,对与frozen可执行文件;8、java类,在Jython版本的Python中;9、.NET组件,在IronPython版本的Python中。C扩展、Jython以及包导入,都不仅仅是简单文件的导入机制的延伸。不过对于导入者来说,完全忽略了需要加载的文件类型之间的差异。
13、更多import的细节可以参考python标准库手册中关于内置的__import__函数的说明。
14、python的第三方扩展,通常使用标准链接库中的distutils工具来自动安装,所以不需要路径设置,就能使用它们的代码。使用distutils的系统一般都附带setup.py脚本,执行这个脚本可以进行程序的安装。这个脚本会导入并使用distutils模块,将这种系统放在属于模块自动搜索路径一部分的目录内(通常在python安装目录树下的Lib\site-packages子目录中)。还有第三方开源的eggs系统。
二、模块代码编写基础(22章)
1、模块的创建:定义模块,只要使用文本编辑器,把一些python代码是输入至文本文件中,然后以".py"为后缀名进行保存,任何此类文件都会被自动认为是python模块。模块顶层指定的所有变量名都会变成其属性(与模块对象结合的变量名),并且可以导出供客户端使用。模块怎么命名都无所谓,如果想要被导入,就应该以.py结尾。对于会执行但不会被导入的顶层文件而言,.py从技术上说可有可无,不过每次都加上去,可以确保文件类型更醒目,并允许以后可以导入任何文件。模块的变量名需要遵循第11章的变量名规则,如:只能包含字母、数字、下划线,而且不能与保留字冲突。而且包的目录也不能包含平台特定的语法,比如,名称中有空格。
2、当一个模块被导入时,python会把内部模块名映射到外部文件名,也就是通过把模块搜索路径中的目录路径加到前面,而.py或其他后缀名添加到后面。例如名为M的模块最后会映射到某个包含模块程序代码的外部文件:<directory>\M.<extension>。使用C或CPP这类外部语言编写的代码来创建python模块,这类模块称为扩展模块,一般是在脚本中作为包含外部扩展库来使用的。
3、模块的使用:客户端可以执行import惑from语句,以使用我们刚才编写的简单模块文件。如果模块没有加载,这两个语句就会去搜索,编译以及执行模块文件程序。主要差别在于,import会读取整个模块,所以必须定义后才能读取它的变量名;from将获取(或者说复制)模块特定的变量名。
4、import是将一个变量名引用整个模块对象,所以必须通过模块名称来得到该模块的属性;而from是将变量名复制到另一个作用域,所以它就可以让我们直接在脚本中使用复制后的变量名,而不需要通过模块。
5、from*语句,当我们使用*时,会取得模块顶层所有赋了值的变量名的拷贝。这里举个例子:
,
技术上说,import和from语句都会使用相同的导入操作。from*形式只是多加个步骤,把模块中所有的变量名复制到了进行导入的作用域内。从根本上说,这就是把一个模块的命名空间融入另一个模块之中;同样的,实际效果就是可以让我们少输入一些。下面介绍一些模块的特性
6、导入只发生一次:模块会在第一次import或from时载入并执行,并且只在第一次如此。这是有意为之的,因为该操作开销较大,在默认的情况下,python只对每个文件的每个进程做一次操作,之后的导入操作都只会取出已加载的模块对象。因为模块文件中的顶层程序代码通常只执行一次,就可以凭借这种特性对变量进行初始化。比如在文件simple.py中有代码:
,
此例中print和=语句在模块的第一次导入的时候执行,而变量spam也在导入的时候初始化:
,
第二次和其后的导入并不会重新执行此模块中的代码,只是从内部模块表中取出已创建的模块对象。因此,变量spam不会再进行初始化。
7、和def一样,import和from是可执行语句,而不是编译期间的声明,而且它们可以嵌套在if测试中,出现在函数def之中等,直到执行程序时,python执行到这些语句,才会进行解析。换句话说,被导入的模块和变量名,直到它们所对应的import或from语句执行后,才可以使用。此外,像def一样,import和from都是隐性的赋值语句。:a、import将整个模块对象赋值给一个变量名;b、from将一个或多个变量名赋值给另一个模块中同名的对象。之前说的关于赋值语句方面的内容,也适用于模块的读取。例如:以from复制的变量名会变成对共享对象的引用。就像函数的参数,对已取出的变量名重新赋值,对其复制之处的模块并没有影响,但是修改一个已取出的可变对象,则会影响导入的模块内的对象,下面是文件small.py的代码:
,
上面第一部分代码中,x不是一个共享的可变对象(数字),但是y是(列表),导入者中的变量名y和被导入者都引用相同的列表对象,所以在其中一个地方修改,也会影响另一个地方的这个对象,如上面第二部分。如之前的图18-1,只要将“调用者”和“函数”换成“被导入模块”和“导入者”即可。
8、以from复制而来的额变量名和其来源的文件之间并没有联系,为了实际修改另一个文件中的全局变量名,必须使用import。
这里与之前的y【0】修改是不同的,这里是修改一个对象,而不是变量名。
9、import和from的对等性:在上一个例子中,我们需要在from后执行import语句,来获取small模块的变量名。from只是把变量名从一个模块复制到另一个模块,并不会对模块名本身进行复制。至少从概念上来说,一个像这样的from语句:
,
与下面的这些语句等效的:
。就像所有赋值语句一样,from语句会遭导入者中创建新变量,而那些变量初始化时引用了导入文件中的同名对象。不过只有变量名被复制出来,而非模块本身。当我们使用语句from ×这种形式时(from module import *),等效的写法是一样的,只不过是模块中所有的顶层变量名都会以这种方式复制到进行导入的作用域中。ps:from的第一步也是普通的导入操作,因此,from总是会把整个模块导入到内存中(如果还没被导入的话),无论是从这个文件中复制出多少变量名。只加载模块文件的一部分(例如,一个函数)是不可能的,但是因为模块在python之中是字节码而不是机器码,通常可以忽略效率的问题。
10、from语句潜在的陷阱:相对来说,from语句会让变量位置更隐秘和模糊,其有破坏命名空间的潜质,至少从理论上来说是这样的。如果使用from导入变量,而那些变量碰巧和作用域中现有的变量同名,那么就会悄悄的覆盖原有的。使用简单的import语句就不存在这样的问题,因为这样需要通过模块名才能获取其内容(module.attr不会和作用域内名为attr的变量相冲突);另一方面,和reload调用同时使用时,from语句有比较严重的问题,因为导入的变量名可能引用之前版本的对象。在这,from module import ×形式的确可能破坏命名空间,让变量名难以理解,尤其是在导入一个以上的文件时。在这种情况下,没有办法看出一个变量名来自哪个模块,只能搜索外部的源代码文件。事实上,from ×形式会把一个命名空间融入到另一个,所以会使得模块的命名空间分割特性失效。
11、何时使用import:当必须使用两个不同模块内定义的相同变量名的变量时,才真的必须使用import,这种情况下不能使用from。
12、模块命名空间:模块最好理解为变量名的封装,也就是定义想让系统其余部分看见变量名的场所。从技术上来说,模块通常相应于文件,而python会建立模块对象,以包含模块文件内所赋值的所有变量名。但是简单来说,模块就是命名空间,而存在于模块之内的变量名就是模块对象的属性。
13、文件生成命名空间:在模块文件顶层每一个赋值了的变量名都会变成该模块的属性。:a、模块语句会在首次导入时执行,在第一次导入时无论在什么地方,python都会建立空的模块对象,并逐一执行该模块文件内的语句,依照文件从头到尾的顺序;b、顶层的赋值语句会创建模块属性,在导入时,文件顶层(不再def或class之内)赋值变量的语句(例如=和def),会建立模块对象的属性,赋值的变量名会存储在模块的命名空间内;c、模块的命名空间通过属性__dict__或dir(M)获取,由导入而建立的模块的命名空间是字典,dir函数大致与对象的__dir__属性的键排序后的列表相等,但是它还包含了类继承的变量名;d、模块是一个独立的作用域(本地变量就是全局变量)。
14、假设有个文件叫做module2.py:
,这个模块首次导入时,python会从头到尾执行其中的语句,有些语句会在模块命名空间内创建变量名,也就是副作用,而其他的语句在导入进行时则会做一些实际工作。例如这两个print会在导入的时候执行:
,
一旦模块加载后,它的作用域就变成模块对象(由import取得)的属性的命名空间,然后,我们可以结合其模块名,通过它来获取命名空间内的属性:
,
,
,
,
此处,sys、name、func以及klass都是在模块语句执行时赋值的,所以在导入后都变成了属性。这里注意到sys属性:import语句其实是吧模块对象赋值给变量名,而文件顶层对任意类型赋值了的变量名,都会产生模块属性。
15、接14:在内部模块命名空间是作为字典对象进行存储的,他们只是普通字典对象,有通用的字典方法可以使用。可以通过模块的__dict__属性获取模块命名空间字典:
,
在模块文件中赋值的变量名,在内部成了字典的键。所以这里的多数的变量名都反映了文件中顶层的赋值语句。不过python也会在模块命名空间中加一些变量名。例如__file__指明模块从那个文件加载,而__name__则指明导入者的名称。
16、属性名的点号运算:在python中,可以使用点号运算语法object.attribute获取任意的object的attribute属性。点号运算其实就是表达式,传回和对象相配的属性名的值。表达式module2.sys会取出module2中赋值给sys的值,同样的,如果我们有内置的列表对象L;而L.append会返回和该列表相关联的append方法对象。
17、重载模块:如果想强制使模块代码重新载入并重新运行,得刻意要求python这么做,也就是调用reload内置函数:a、导入(import或from语句)只会模块在流程中第一次导入时,加载和执行该模块的代码;b、之后的导入只会使用已加载的模块对象,而不会重载或重新执行文件的代码;c、reload函数会强制已加载的模块的代码重新载入并重新执行。此文件中新的代码的赋值语句会在适当的地方修改现有的模块对象。ps:reload当前只能用在python编写的模块;用c这类语言编写的编译后的扩张模块也可在执行中动态加载,但无法重载。再次ps:在2.6中reload作为一个内置函数使用,在3.0中,它已经移入了imp标准库模块中,叫做imp.reload。这意味着,需要一条额外的import语句或from语句来载入该工具。
18、与import和from不同的是:a、reload是python中的内置函数,而不是语句;b、传给reload的是已经存在的模块对象,而不是变量名;c、reload在3.0中位于模块之中,并且必须导入自己。
。
一般用法:导入一个模块,在文本编辑器内修改其源代码,然后将其重载。当调用reload时,python会重读模块文件的源代码,重新执行其顶层语句。reload会在适当的地方修改模块对象,reload并不会删除并重建模块对象。因此,程序中任何引用该模块对象的地方,自动会受到reload的影响:a、reload会在模块当前命名空间内执行模块文件的新代码,重新执行模块文件的代码会覆盖其现有的命名空间,并非进行删除而进行重建;b、文件中顶层赋值语句会使得变量名换成新值,重新执行的def语句会因重新赋值函数变量名而取代模块命名空间内该函数之前的版本;c、重载会影响所有实用import读取了模块的客户端;d、重载只会对以后使用from的客户端造成影响,之前使用from来读取属性的客户端并不会受到重载的影响,那些客户端引用的依然是重载前所取出的旧对象。
三、模块包(23章)
除了模块名之外,导入也可以指定目录路径。python代码的目录就称为包,因此,这类导入就称为包导入,事实上,包导入是把计算机上的目录变成另一个python命名空间,而属性则对应于目录中所包含的子目录和模块文件。这对于组织大型系统内的文件会很方便,而且可以简化模块搜索路径的设置。我们知道,多个相同名称的程序文件安装在某一个机器上时,包导入也可以偶尔用来解决导入的不确定性。由于它只与包中的代码相关,在这里,还会介绍python最近的相对导入模块和语法,这种方法修改了搜索路径并且扩展了在包中用于导入的from语句。
1、包导入基础:在import语句中列举简单文件名的地方,可以改成列出路径的名称,彼此以点号相隔:,from语句也是一样:
,这些语句中的“点号”路径是对应于机器上目录层次的路径,通过这个路径可以获得到文件mod.py(或类似文件,扩展名可能会变化)。也就是说,上面的语句是表面了机器上有个目录dir1,而dir1里有子目录dir2,而dir2内含有一个名为mod.py(或类似文件)的模块文件。此外,这些导入意味着,dir1位在某个容器目录dir0中,这个目录可以在python模块搜索路径中找到。换句话说,这两个import语句代表了这样的目录结构(以dos反斜线风格字符显示):
容器目录dir0需要添加在模块搜索路径中(除非这是顶层文件的主目录),就好像dir1是模块文件那样。一般的,包导入路径中最左边的部分仍然是相对于在21章介绍的sys.path模块搜索路径列表中的一个目录。从此以后,脚本内的import语句明确指出找到模块的目录路径。
2、包和搜索路径设置:如果使用这个方法,记得,import语句中的目录路径只能是以点号间隔的变量。不能在import语句中使用任何平台特定的路径语法:例如:C:\dir1、My Documents.dir2或.../dir1:这些从语法上是行不通的,所需要做的就是:在模块搜索路径设置中使用平台特定的语法,来定义容器的目录。例如下面的是不对的:,增加C:\mycode在PYTHONPATH系统变量中或是.pth文件中,并且这样描述:
,实际上,模块搜索路径上的项目提供了平台特定的目录路径前缀,之后再在import的那些路径左边添加了这些路径。import 语句以与平台不相关的方式,提供了目录路径写法。
3、__init__.py包文件:如果选择包导入,就需要多一条规则:包导入语句的路径中的每个目录内都必须有__init__.py这个文件,否则导入会失败。也就是说上述的例子中dir1和dir2内都必须有这个文件。容器目录dir0不需要这类文件,因为其本身没列在import语句之中。正式的来说,如这样的目录结构:,以及这种形式的import语句:
,都必须遵循下列规则:a、dir1和dir2中必须都含有__init__.py文件;b、dir0是容器,不需要__init__.py 文件;如果有的话,这个文件也会被忽略掉;c、dir0(而非dir0\dir1)必须列在模块搜索路径上(也就是此目录必须是主目录,或者列在PYTHONPATH中)。结果就是这个例子的目录结构如下:
,
__init__.py可以包含python程序代码,就像普通模块文件。这类文件从某种程度上讲就像是python的一种声明,尽管如此,可以完全是空的。作为声明,这些文件可以防止有相同名称的目录不小心隐藏在模块搜索路径中,而之后才出现真正需要的模块。没有这层保护,python可能会挑选出和程序代码无关的目录,只是因为有一个同名的目录刚好出现在搜索路径上位置较前的目录内。更通常的情况下,__init__.py文件扮演了包初始化的钩子,替目录产生模块命名空间以及使用目录导入时实现from*(也就是from ... import *)行为的角色。
4、接上3,:a、包的初始化:python首次导入某个目录时,会自动执行该目录下__init__.py文件中所有程序代码。因此,这类文件自然就是放置包内文件所需要初始化的代码的场所。例如,包可以使用其初始化文件,来创建所需要的数据文件,链接数据库等。一般而言,如果直接执行,__init__.py文件没什么用,当包首次读取时,就会自动运行;b、模块命名空间的初始化,在包导入的模型中,脚本内的目录路径,在导入后会变成真实的嵌套对象路径。例如上一个例子,导入后,表达式dir1.dir2会运行,并返回一个模块对象,而此对象的命名空间包含了dir2的__init__.py文件所赋值的所有变量名。这类文件为目录(没有实际相配的模块文件)所创建的模块对象提供了命名空间;c、from*语句的行为:可以在__init__.py文件内使用__all__列表来定义目录以from*导入时,需要导出什么。在这个文件中,__all__列表是指当包(目录)名称使用from*时,应该导入的子模块的名称清单。如果没有设定__all__,from*语句不会自动加载嵌套于该目录内的子模块。取而代之的是,只加载该目录的__init__。批阅文件中赋值语句定义的变量名,包括该文件中程序代码明确导入的任何子模块。例如,某目录中__init__.py内的语句from submodule import X,会让变量名X可在该目录的命名空间内使用(第24章介绍__all__另一种用法)。
5、这里通过例子来说明初始化文件和路径是如何运作的。厦门三个文件分别位于目录dir1和dir1的子目录dir2中:
,
,
这里的dir1要么是我们工作所在的目录(主目录)的子目录,要么就是位于模块搜索路径(sys.path)的一个目录的子目录。无论哪种,dir1的容器都不需要__init__.py。当向下搜索路径时,import语句会在每个目录首次遍历时,执行该目录的初始化文件。print语句加在这里,用来跟踪它们的执行。此外,就像模块文件一样,任何已导入的目录也可以传递给reload,来强制该项目重新执行。就像这里显示的,reload可以接受点号路径名称,来重载嵌套的目录和文件:
,
导入后,import语句内的路径会变成脚本的嵌套对象路径。在这里,mod是对象,嵌套在对象dir2中,而dir2又嵌套唉对象dir1中:
,
路径中每个目录名称都会变成赋值了模块对象的变量,而模块对象的命名空间则是由该目录内的__init__.py文件中所有赋值语句进行初始化的。dir1.x引用了变量x,x是在dir1\__init__.py中赋值的,而mod.z引用的变量z则是在mod.py内赋值的:
。
6、包对应的from语句和import语句:import和包一起使用时,有些不方便,比如想要直接读取dir2惑mod会出错,需要输入完整路径:
,
所以让包使用from语句,避免每次读取时都得重新输入路径,更重要的是,如果重新改变目录树结构,from语句只需在程序代码中更新一次路径,而import需要该很多地方。import作为一个扩展功能,在这里也有一定的帮助,它提供一个完整路径较短的同义词:
。
7、为什么要使用包导入:在较大的程序中,包让导入更具信息性,并可以作为组织工具,简化模块的搜索路径,而且可以解决模糊性。首先,因为包导入提供了程序文件的目录信息,因此可以轻松的找到文件,从而可以作为组织工具来使用。没有包导入时,通常得通过查看模块搜索路径才能找出文件。再者,如果根据功能把文件组织成子目录,包导入会让模块扮演的角色更为明显,也使代码更具可读性。例如正常导入模块搜索路径上某个目录内的文件时,就像这样:,与下面包含路径的导入相比,提供的信息就更少:
,包导入也可以简化PYTHONPATH和.pth文件搜索路径设置。实际上,如果所有跨目录的导入,都使用包导入,并让这些包导入都相对于一个共同的根目录,把所有的python程序代码都存在其中,在搜索路径上就只需要一个单独的接入点:通用的根目录。
8、在实际中需要包导入的场合,也就是需要解决当多个同名程序文件安装在同一个机器上时,所引发的模糊性。假设一个程序中,包含文件utilities.py,其中包含了通用的工具代码,还有个顶层文件main.py让用户来启动程序。这个程序的文件会以import utilities加载并使用通用的代码。当程序分发给用户时,采用的是.tar或.zip文件的形式,其中包含了该程序的所有文件,而当它在安装时,会把所有的文件解压放到目标机器上一个叫做system1的目录中:
;
现在假设第二个程序员开发了另一个不同的程序,而其文件命名也是utilities.py和main.py,而且同样在程序文件中使用import utilities来加载一般的代码文件。当获得第二个系统并安装在和第一个系统相同的计算机上时,它的文件会解压并安装至接收机器上某处文件夹名为system2的新目录下,不会与第一个系统同名:
,
目前为止,暂时没任何问题。但是实际上,暂时不需要配置模块搜索路径。因为python总是先搜索主目录(也就是包含顶层文件的目录),任一个系统内的文件的导入,都会字典看见该系统目录内所有的文件。例如:如果点击system1\main.py,所有的导入都会先搜索system1.同样的如果启动system2\main.py,则会改为先搜索system2.记住,只有在跨目录进行导入时,才需要模块搜索路径的设置。尽管如此,假设这两套都安装了,你想在自己系统内使用每一个utilities.py中的一些代码。那么就想在第三个目录(自己的目录)内编写的文件内写下下面的代码,来载入两个文件中的一个:
,
那么问题来了,怎么设置路径。因为搜索路径本质上是线性的,总是从左到右,所以不管如何只能导入一个目录下的那个文件,没法接着自动导入另一个。在每次导入操作时,试着在脚本内修改sys.path。不过这是外部的工作,容易出错。解决这个问题的方法就是用包。不要在单独的目录内把文件安装成单纯的文件列表,而是将它们打包,在共同跟目录之下,安装成子目录。例如,可能想要有厦下面的安装层次:
,
现在,就是将共同的root目录添加到搜索路径中,如果程序代码导入就像对于这个通用的根目录,就能以包导入,导入任何一个系统的工具文件:该文件所在目录名称会使其路径具有唯一性(引用的模块也是一样)。事实上,只要使用import语句,就可以在同一个模块内导入这两个工具文件,而每次引用工具模块时,都要重复其完整的路径:
,
这里所有的目录名称让模块的引用变得具有唯一性。ps:如果需要读取两个惑两个以上路径的同名属性时,才需要使用import,在这种情况下不能用from。如果被调用的函数名称在每个路径内都不同,from语句就可以避免每当调用其中一个函数时,就得重复完整的包的路径的问题。注意到,在前面所展示的安装层次中,__init__.py文件已经加入到system1和system2目录中来使其工作,但是不需要在根目录内增加。只有在程序代码内,import语句所列的目录才需要这些文件。python首次通过包的目录处理导入时,这些文件就会自动运行了。不过system3本来不需要放在根目录下的,不过为了自己写的也可以被后续的人使用,按照通用性来说,就是这样。
9、包相对导入:在包自身的内部,包文件的导入可以使用和外部导入相同的路径语法。但是它们也可能使用特殊的包内搜索规则来简化导入语句。也就是说,包内的导入可能相对于包,而不是列出包导入路径。在2.6中,首先在导入上隐式的搜索包目录而3.0需要显式的相对导入语法。
10、接上9,在3.0中包内导入操作的方式略有变化,:a、它修改了模块导入搜索路径语义,以默认的跳过包自己的目录。导入只是检查搜索路径的其他组件。这叫做“绝对导入”;b、它扩展了from语句的语法,以允许显式的要求导入只搜索包的目录,这叫做“相对”导入语法。新的from语句相对语法在3.0中可以使用,但是默认的搜索路径的变化必须作为一个选项打开。也就是在3.0中,通常必须使用特殊的from语法来导入与导入者位于同一包中的模块,除非你从一个包根目录拼出一个完整的路径。没有这一语法,包不会字典搜索到。
11、相对导入基础知识:在3.0和2.6中,from语句现在可以使用前面的点号(“.”)来指定,它们需要位于同一包中的模块(所谓的包相对导入),而不是位于模块导入搜索路径上某处的模块(绝对导入):a、在3.0和2.6中,使用from语句前面的点号来表示,导入应该相对于外围的包---这样的导入将只是在包的内部搜索,并且不会搜索位于导入搜索路径(sys.path)上某处的同名模块。直接效果是包模块覆盖了外部的模块;b、在2.6中,包的代码中的常规导入(没有前面的点),目前默认为一种先相对再绝对的搜索路径顺序,也就是说,它们首先搜索包自己的路径,然而,在3.0中,在一个包中导入默认是绝对的----在缺少任何特殊的点语法的时候,导入忽略了包含包自身并在sys.path搜索路径上的某处查找。
12、接上11:例如在3.0和2.6中,如下的语句:,告诉python把位于与语句中给出的文件相同包路径中的名为spam的一个模块导入。类似的语句:
,意味着“从名为spam的模块导入变量name,而这个spam模块与包含这条语句的文件位于同一个包下。“没有前面的点号的一条语句的行为,取决于使用的python版本的不同:a、在2.6中,这样的一条import将会仍然默认为当前的先相对在绝对的搜索路径顺序(例如:先搜索包的目录,除非在导入文件中包含了如下的一条语句):
。如果有这条语句,它打开了3.0的默认绝对搜索路径变化,正如下面说的:在3.0中,不带点号的一个import总是会引发python略过模块导入搜索路径的相对部分,并且在sys.path所包含的绝对目录中查找。例如:在3.0的方式中,如下的形式的一条语句,总是在sys.path上某处查找一个string模块,而不是查找该包中具有相同名称的模块:
,没有了2.6中的from __future__语句,如果包中有一个字符串模块,将会导入它。当导入修改打开的时候,要在3.0和2.6中得到相同的行为,运行下面的语句来强制一个相对导入:
,现在这在2.6和3.0中都有效。3.0中的方式的唯一区别是,当模块给定一个简单的名称,要把文件所在的同样的包目录中的该模块载入,它是必须的方式。前面的点号只能用在from中,不能用在import中。3.0中,import moduname语句形式如今仍然执行相对导入(例如:首先搜索包的目录),但是,在2.7中,这也会变成绝对的。前面没有点号的from与import的行为一致,在3.0中是绝对的(略过包目录),并且在2.6中是先相对在绝对(先搜索包目录)。其他的基于点的相对引用模式也是可能的。在位于名为mypkg的一个包目录中的一个模块文件中,如下替代的import形式也像所述的那样工作:
。
13、为什么使用相对导入:开始设计的时候,是为了当脚本在同名文件出现在模块搜索路径上许多地方时,可以解决模糊性,如如下包的目录:
,
定义一个名为mypkg的包,其中含有mypkg.main和 mypkg.string的模块。现在假设main试图导入名为string的模块。在2.6和稍早版本中,python会先寻找mypkg目录以执行相对导入。这会找到并导入位于该处的string.py文件,将其赋值给mypkg.main模块命名空间内的变量名string。不过import的愿意可能是要导入python标准库的string模块。可惜这里没办法直接忽略mypkg.string而去寻找位于模块搜索路径更右侧的标准库中的string模块。此外,也无法使用包导入路径来解决这个问题。因为无法依赖每台机器上都存在标准链接库以上的额外包的目录结构。也就是说,包中的导入可能是模糊的。在包内。import spam语句是指包内或包外的块,这没有明确。也就是说一个局部的模块或包可能会隐藏sys.path上的另一个模块。
14、接13,3.0中相对导入问题的解决方案:在包中运行的导入已经能够在3.0(作为2.6的一个选项)改为绝对的。在这种方式下,在上述的示例文件mypkg/main.py中,一条如下的import语句将总是在包外面找string,通过sys.path的绝对导入搜索:,没有前面的点号的from,也看作是绝对的:
,如果想要从包中导入一个模块,而没有给出从包根目录的完整路径,在from语句中使用点语法仍然可能做到相对导入:
。这种形式只相对于当前的包导入string模块,并且是前面的导入示例的绝对形式的相对等价形式,当使用这特殊的相对语法的时候,包的目录是唯一搜索的目录。也可以使用相对语法从一个模块复制特定的名称:
,这条语句再次相对于当前包来引用string模块。如果这段代码出现在,ypkg.main模块中,它将从mypkg.string导入name1和name2.。实际上,相对导入中的“.”用来表示包含文件的包目录,而导入就出现在该文件中。前面再增加一个点,将执行从当前包的父母来的相对导入。例如:
,将会导入mypkg的一个兄弟,位于该包自己的包含目录中的spam模块,该模块紧挨着mypkg。更通俗的说,位于某个模块A.B.C中的代码可以做下面任何一种导入:
15、相对导入VS绝对导入:一个文件有时候也可以在一条绝对导入语句中显式的指定其包。例如:下面的语句中,将在sys.path的一个绝对路径中找到mypkg:,不过这依赖配置以及模块搜索路径设置的顺序,尽管相对导入的点语法不会依赖于此。这种形式需要直接包含将要包含在模块搜索路径中的mypkg。通常,像这样显式的指定包的时候,绝对导入语句必须列出包的根目录下所有目录:
,在较大或较深的包中,这可能比点语法要做更多的工作:
16、相对导入的作用域:a、相对导入适用于只在包内导入,这种功能的模块搜索路径修改只应用于位于包内的模块文件中的import语句。位于包外的通常的导入像前面介绍的那样工作,首先自动搜索包含顶级脚本的目录;b、相对导入只适用于from语句:一个from中的模块名前面有一个或多个点号,包含点号但是前面没有个点号的模块名是包导入,而不是相对导入;c、术语含糊不清:实际上,所有的导入对于某事物来说都是相对的。在包外部,包仍然是相对于sys.path搜索路径上列出的目录的。其包含了程序的包含目录、PYTHONPATH设置、路径文件设置以及标准库。当交互的工作时,该程序的包含目录直接就是当前的工作目录;对于包内部的导入,2.6首先搜索包自身增强这一行为,在3.0中,真正变化的只是常规的“绝对”导入语法忽略了一个包目录,但是特殊的“相对”导入语法使其首先搜索并仅搜索它。当说3.0的导入是“绝对的”,我们的意思是相对sys.path上的目录的,而不是相对于包自身。相反,当提及“相对”导入,意思是它们只是相对于包目录的。当然,一些sys.path目录是绝对路径或相对路径。换句话说,3.0中的“包相对导入”真的只是归结为删除了2.6针对包的特殊搜索路径行为,并且添加了特殊的from语法来显式的要求相对行为。如果过去编写自己的包导入而不依赖于2.6的特殊隐式相对查找(例如,总是通过拼写出从一个包根目录出发的完整路径),这种修改很大程度上是没有意义的。如果不这么做,将需要更新自己的包文件,以使用针对本地包文件的新的from语法。
17、模块查找规则总结:使用包导入和相对导入,3.0中的模块查找可以完整的概括为下面几条:a、简单模块名(例如,A)通过搜索sys.path路径列表上的每个目录来查找,从左到右进行。这个列表由系统默认设置和用户配置组成;b、包是带有一个特殊的__init__.py文件的python摸块的直接目录,这使得一个导入中可以使用A.B.C目录路径语法。在A.B.C的一条导入中,名为A的目录位于相对sys.path的常规模块导入搜索,B是A中的另一个包子目录,C是一个模块或B中的其他可导入项;c、在一个包文件中,常规的import语句使用和其他地方的导入一样的sys.path搜索规则。包中的导入使用from语句以及前面的点号,然而,它是相对于包的;也就是说,只检查目录,并且不使用常规的sys.path查找。例如,在from . import A中,模块搜索规则限制在包含了该语句中出现的文件的目录之中。
18、相对导入的应用:a、在包之外导入:这一功能不会影响到一个包之外的导入,所以下面的代码还是会预期的查找标准库的string模块:
,
但是,如果所工作的目录中添加一个同名的模块,将会选择该模块,因为模块搜索路径的第一条是当前工作目录(CWD):,
也就是说,常规的导入仍然相对于“主”目录(顶级的脚本的包含目录,或者我们正在工作的目录)。实际上,如果不是在用作一个包的部分的一个文件中,甚至不允许相对导入语法:
。
在这一部分以及所有的示例中,在交互模式中输入的代码将与其放在一个顶层脚本中运行的行为是相同的,因为sys.path上的第一条目要么是交互的工作目录,要么是包含顶层文件的目录。唯一的区别是,sys.path的开始是一个绝对路径,而不是一个空字符串:
。
19,接上18。:b、包内的导入:现在来去除在CWD中编写的本地string模块,并构建带有两个模块的一个包目录,包括必需空的test\pkg\__init__.py文件:
,
这个包中第一个文件试图用一条常规的import语句导入第二个文件。这在2.6中当作相对的,而在3.0中当作绝对的。因此,后者会失败。也就是说,2.6首先搜索包含的包,但是3.0不这么做。这在3.0需要注意的非兼容行为:
,
为了使得在2.6和3.0中都有效,使用特殊的相对导入语法来修改第一个文件,以便其导入在3.0中也搜索包目录:
,
,
,
。
20、接上19,导入仍然是相对于 CWD的:在前面的示例中,包模块仍然必须访问string这样的标准库模块。实际上,它们的导入仍然相对于模块搜索路径的条目,即便这些条目自身是相对的。如果我们再次向CWD添加一个string模块,包中的导入将会在那里找到它,而不是在标准模块库中。尽管可以在3.0中掠过带有一个绝对导入的包目录,仍然不能略过导入包的程序的主目录:
,
。
21、接20,使用相对导入和绝对导入选择模块:为了展示如何应用于标准模块库的导入,再一次重新设置包。去除掉本地string模块,并在包自身之中定义一个新的:
,
这样,获得哪个版本的string模块取决于使用的python版本,和前面一样,3.0把第一个文件中的导入解释为绝对的并略过了该包,不过2.6不会:
。
在3.0中使用相对导入语法会迫使再次搜索包,就好像在2.6中一样----通过在3.0中使用绝对和相对导入语法,可以显式的跳过或选择包目录。实际上,这是3.0方式所解决的情况:
,
。
相对导入语法是一种绑定声明,而不止是一种偏好。如果删除该例中的string.py文件,string.py中的相对导入在3.0和2.6中都会失败,而不是回归到这一模块(或任何其他模块)的标准库版本:
,
相对导入所引用的模块必须在包目录中存在。
22、接上21。导入仍然是相对于CWD的:虽然绝对导入允许我们略过包模块,它们仍然依赖于sys.path上的其他部分。为了最后一次测试,这里先定义两个string模块。在下面的代码中,CWD中有一个为该名称的模块,包中有一个该名称的模块,并且,另一个string模块位于标准库中:
,
当使用绝对语法时,得到不同的版本。2.6将这条语句解释为相对于包的,但3.0将其看作“绝对的“,在这种情况下,真的意味着它略过包并载入相对与CWD的版本(而不是相对于标准库的版本):
。
可以看到尽管包显式的要求目录中的模块,它们的导入仍然是相对于常规模块搜索路径的剩余部分。在这个例子中,程序中的一个文件使用的包隐藏了该包可能想要的标准库模块。3.0中真正发生的变化只是运行包代码在包的内部惑外部选择文件。由于导入的方法可能依赖于无法遇见的封闭环境。3.0中绝对导入并不能保证找到标准库中的模块。
23、包已经是python的标准组成部分,常常见到的较大的第三方扩展都是以包目录形式分发给用户,例如win32akk这个python的windows扩展包,是首先采用包这种形式的。例如需要加载客户端com工具,就需要使用如下的语句:,这会从win32com包的client模块中取出一些变量名。
四、高级模块话题(24章)
这部分介绍了一些更高级的话题:数据隐藏、__future__模块、__name__变量、sys.path修改、列表工具、通过名称字符串来运行模块、过滤式重载等。这部分将会构建一些比目前所见到的更大更有用的工具,它们组合了函数和模块。和函数一样,当模块接口定义良好时,它们要更有效率一些。
1、在模块中隐藏数据:python模块会到处其文件顶层所赋值的所有变量名。没有对某个变量名声明,使其在模块内可见或不可见这种概念。实际上,是没有防止客户端修改模块内变量名的方法的。所以在模块内的数据隐藏是一种惯例,而不是一种语法约束。的确可以通过破坏模块名称来使这个模块不能工作,不过还好没人这么无聊。不过python的封装更像是打包,而不是约束。
2、最小化from* 的破坏: _X 和__all__:把下划线放在变量名前面(例如_X),可以防止客户端使from*语句导入模块名时,把其中的那些变量名复制出去。这其实是为了对命名空间的破坏最小化而已。因为from*会把所有变量名赋值出去,导入者可能得到超出它所需的部分(包括会覆盖导入者内的变量名的变量名)。下划线不是"私有"声明:你还是可以使用其他导入形式看见并修改这类变量名。例如,使用import语句。此外,可以在模块的顶层把变量名的字符串列表赋值给变量__all__,以达到类似于_X命名惯例的隐藏效果,例如:,使用此功能时,from *语句只会把列在__all__列表中的这些变量名复制出来,事实上,这和_X惯例相反:__all__是指出要复制的变量名,而_X是指出不被复制的变量名。Python会先寻找模块内的__all__列表;如果没有定义的话,from *就会复制出开头没有单下划线的所有变量名。就像_X惯例一样,__all__列表只对from *语句这种形式有效,它并不是私有声明,模块的编写者可以使用任何一种技巧实现模块,在碰上from *时,能良好的运行。
3、启用以后的语言特性:这中未来的特性可能破坏现有代码语言方面的变动,默认情况下这种功能是关闭的。要开启这类扩展功能,可以使用像以下形式的特定的import语句:,这个语句一般应该出现在模块文件的顶端(也许在docstring之后),因为这是以每个模块为基础,开启特殊的代码编译。此外,在交互模式下这个语句也是可行的。
4、混合用法模式:__name__和__main__:这是特殊的与模块相关的技巧,可把文件作为成模块导入,并以独立式程序的形式运行。每个模块都有个名为__name__的内置属性,python会自动设置该属性:a、如果文件是以顶层程序文件执行,在启动时,__name__就会设置为字符串"__main__";b、如果文件被导入,__name__就会改设成客户端所了解的模块名。结果就是模块可以检测自己的__name__,来确定它是在执行还是在导入。例如,假设我们简历下面的模块文件,名为runme.py,它只导出了一个名为tester的函数:
,
这个模块定义了一个函数,让用户可以正常的导入并使用:
,
然而,这个模块也在末尾包含了当此文件以程序执行时,就会调用该函数的代码:
,
然而当此文件以程序执行时,就会调用模块末尾的代码:
,
实际上,一个模块的__name__变量充当一个使用模式标志,允许它编写成一个可导入的库和一个顶层脚本。尽管简单,可是这一钩子几乎在可能遇到的每个python程序文件中应用。也许使用__name__测试是最常见的就是自我测试代码。简而言之,就是在文件末尾加个__name__测试,把测试模块导出的程序代码放在模块中。如此以来,就可以继续导入,在客户端使用该文件,而且可以通过检测其逻辑在系统shell中运行。编写既可以作为命令行工具也可以作为工具库使用的文件时,__name__技巧也很好用。例如,假设用python编写了一个文件寻找脚本。如果将其打包成一些函数,而且在文件中加入__name__测试,当此文件独立执行时,就自动调用这些函数,这样就能提高代码的利用效率。
5、以__name__进行单元测试:在第18章中,编写了一个脚本,从一组传进来的参数中计算其最小值:
,
该脚本在末端包含了自我测试程序代码,所以不用每次执行时,都得在交互模式中重新输入所有代码就可以进行测试。不过这种写法的问题在于每次这个文件被另一个文件作为工具导入时,都会出现调用自我测试所得到的输出:可以通过用__name__检查区块诶封装了自我测试的调用,使其在文件作为顶层脚本文件的时候才执行;
,
这会在顶端打印__name__的值,目的是来跟踪它的值。
6、使用带有__name__的命令行参数:这是展示通常使用__name__技巧的另一种方式。如下的模块formats.py,为导入者定义了字符串格式化工具,还检查其名称看它是否作为一个顶层脚本在运行;如果是的话,测试并使用系统命令行上列出的参数来运行一个定制的或传入的测试。在python中,sys.argv列表包含了命令行参数,它是反映在命令行上录入的单词的一个字符串列表,其中,第一项总是将要运行的脚本的名称:
,
该文件可以在22.6和3.0中工作,当直接运行时,像前面那样测试自己,但是使用命令行上的选项来控制测试行为。:
,
。
因为这个代码使用了双模式,所以也可以作为库的部分导入到其他环境中:
,
因为该代码中有文档字符串,所以可以使用help函数来研究其工具:
,
可以参考python的标准库和手册中的getopt和optparse模块。
7、修改模块搜索路径:之前说过模块搜索路径是一个目录列表,但是如何让python程序本身来修改搜索路径呢,也就是修改名为sys.path的内置列表。sys.path在程序启动时就会进行初始化,当在那之后,可以随意对其元素进行删除、附加和重设:
,
一旦做了这种修改,就会对python程序中将要导入的地方产生影响,因为所有导入和文件都共享了同一个sys.path列表。事实上,这个列表可以任意修改:
,
所以可以这样在程序中动态配置搜索路径。不过如果从路径中删除重要目录,那么就无法获取一些关键的工具了。例如,上一个例子中,删除python的源代码库目录的话,就无法获取string模块了。ps:sys.path只在修改python的会话或程序(进程)中才会存在,当python结束,是不会保留下来的。PYTHONPATH和.pth文件路径配置是保存在操作系统中,而不是执行中的python程序。因此使用这种配置方法更全局一些:机器上的每个程序都会去查找PYTHONPATH和.pth,而且在程序结束后,还会存在
8、import语句和from语句的as扩展:这两个都可以扩展,让模块可以在脚本中给予补贴的变量名:,相当于:
,
在这类import之后,就可以(是必须)使用列在as之后的变量名来引用该模块。from语句也可以这么用,把从某个文件导入的变量名,赋值给脚本中的不同的变量名:,这个扩展功能很常用,替变量名较长的变量提供简短一些的同义词,而且当已在脚本中使用一个变量名使得执行普通import语句会被覆盖时,使用as,就可以避免变量名冲突:
,
使用第23章所提到的包导入功能时,也可以为整个目录路径提供简短、简单的名称,十分方便:
。
9、模块是对象:元程序:模块是通过内置属性显示了它们的大多数有趣的特性,所以可以很容易编写程序来管理其他程序。通常称这类管理程序为元程序,因为他们是在其他系统上工作的。这也成为内省(introspection),因为程序能看见和处理对象的内部。内省是高级功能,但是它们可以用作创建程序的工具。例如:要取得M模块内名为name的属性,可以使用结合点号运算,或者对模块的属性字典进行索引运算(在内置__dict__属性中显示)。python也在sys.modules子弟啊中导出所有已加载的模块的列表(也就是sys模块的modules肃花絮),并提供内置函数getattr,用字符串名来取出属性(比如object.attr,而attr是运行时的字符串),所以下面的结果是相同的:
,
这样可以帮助用来建立关于程序的程序。例如:以下是名为mydir.py的模块,运用这些概念,可以实现定制版本的内置函数dir。它定义并导出一个名为listing的函数,这个函数以模块对象为参数,打印该模块命名空间的格式化列表:
,
注意顶端的文档字符串,就像在前面的formats.py示例中一样,因为可能想要将其用作一个通用的工具,编写一个文档字符串来提供通过__doc_-属性或help函数可以访问的功能性信息:
,
在这个模块的最后也提供了自测试逻辑,导入并列出自己。这里给出了python3.0中产生的输出(如果要在2.6中使用,先使用__future__ import导入来激活3.0print调用,end关键字只用于3.0):
,
要使用这一工具来列出其他的模块,直接把模块作为对象传入到这个文件的函数中。这里,列出了标准库中的tkinter GUI模块中的属性:
。
mydir是一个可以浏览其它程序的程序。
10、用名称字符串导入模块:我们无法使用import来直接载入以字符串形式给出其名称的一个模块,python期待一个变量名称,而不是字符串:
,
直接把该字符串赋给一个变量名称也是无效的:
。
这里是尝试导入一个文件x.py,而不是string模块---一条import语句中的名称既变成了赋给载入的模块的一个变量,也从字面上标识了该外部文件。所以需要使用特殊的工具,从运行时生成一个字符串来动态的载入一个模块。比如,把一条导入语句构建为python代码的一个字符串,并且将其传递给exec内置函数运行:
。
exec函数(极其近亲eval)编译一个代码字符串,并将其传递给python解释器以执行。在python中,字节代码编译器在运行时可以使用,因此,我们像这样编写构建和运行其他程序的程序,默认情况下,exec运行当前作用域中的代码,但是,可以通过传入可选的命名空间字典来更加具体的应用。exec的唯一的缺点就是,每次运行时必须编译import语句,苏若运行多次,使用内置的__import__函数来从一个名称字符串载入的话,代码可能运行的更快。效果是类似的,但是__import__运行模块对象,所以这里将其赋给一个名称以保存它:
。
11、过渡性模块重载:第22章的模块重载,这是选择代码中的修改而不需要停止或重启一个程序的一种方式。当重载一个模块时,python只重新载入特殊模块的文件,它不会自动重载那些为了导入要重载文件的模块。例如,如果要重载某个模块A,并且A导入模块B和C,重载只适用于A,而不适用于B和C。A中导入B和C的语句在重载的时候重新运行。但是它们只是获取已经载入B和C模块对象(假设之前已经导入了)。在实际的代码中,文件A.py如下:
,
也就是无法在默认情况下来过渡性的选择程序中的所有模块中的修改;相反,必须使用多次reload调用来独立的更新子部分。对于交互测试的大系统而言,工作量很大。可以通过在A这样的父模块中添加reload调用,从而设计自己的系统能够自动重载它们的子部分,但是这会使模块的代码变复杂。更好的方法就是编写一个通用的工具来自动进行过渡性重载,通过扫描模块的__dict__属性并检查每一项的type以找到要重新载入的嵌套模块。这样的一个工具应该递归的调用自己,来导航任意形式的导入依赖性链条。模块__dict__属性在前面介绍过,并且也介绍过type调用。这里只需要将两者结合。例如,下面列出的模块reloadall.py有一个reload_all函数来自动地重载一个模块,以及该模块导入的每个模块等,所有通往每个导入链条最底端的通路都被考虑到。使用字典来记录已经重载的模块,递归地便利导入链条,以及标准库的types模块,该模块直接为内置类型预定义type结果。访问字典的技术在这里用来在导入是递归或冗余的时候避免循环,因为模块对象可以是字典键:
,
要使用这一工具,导入其reload_all函数并将一个已经载入的模块的名称传递给它。当文件独立的运行,其自测试代码将会测试自己,它必须导入自己,因为它自己的名字并没有在没有一个导入的文件中定义(在3.0和2.6中都有效,并且打印出相同的输出,因为已经在print中使用了+而不是逗号):
。
下面的是这个模块对于3.0中某些标准库模块工作的情况。os是如何由tkinter导入的,但是tkinter在os之前已经导入了sys:
,
接下来的会话
展示了常规重载和过渡性重载的对比效果---除非使用过渡性工具,否则重载不会选取对两个嵌套的文件的修改。
12、模块设计理念:a、总是在模块内编写代码;b、模块偶尔要降到最低:全局变量,如果编写成闭合的盒子,模块运行的最好,所以模块应尽可能和其他模块的全局变量无关,除了与从模块导入的函数和类;c、最大化模块的粘合性:统一目标,可以通过最大化模块的粘合性来最小化模块的耦合性。如果模块的所有元素都享有共同的目的,就不太可能依赖外部的变量名;d、模块应该少去修改其他模块的变量。图24-1描绘了模块操作的环境。模块包含变量、函数、类以及其他的模块,函数有自己的本地变量。
模块是被导入的,但模块也会导入和使用其他模块,这些模块可以用python或其他语言写成。模块可内行变量、函数以及类来进行其工作,而函数和类可包含变量和其他元素。不过,从最顶端来看,程序也只是一个模块的集合而已。
13、模块陷阱:a、顶层代码的语句次序的重要性,当模块首次导入(或重载)时,python会从头到尾执行语句:1、在导入时,模块文件顶层的程序代码(不再函数内)一旦python运行到时,就会立刻执行。因此,该语句是无法引用文件后面位置赋值的变量名;2、位于函数主体内的代码直到函数被调用后才会运行。因为函数内的变量名在函数实际执行前都不会解析,通常可以引用文件内任意地方的变量。一般来说,前向引用只对立即执行的顶层模块代码有影响,函数可以任意引用变量名,下面是例子:
,
当这个文件导入时(或者作为独立程序运行时),python会从头到尾运行它的语句。对func1的首次调用失败,因为func1 def 尚未执行。只要func1调用时,func2的def已运行过,在func1内对func2的调用就没问题。文件最后对func1的调用可以工作,因为func1和func2都已经赋值了。在顶层程序内混用def不仅难读,也造成了对语句顺序的依赖性。作为一条原则,如果需要把立即执行的代码和def一起混用,就要把def放在文件前面,把顶层代码放在后面。
14、接13,b、from复制变量名,而不是连接:from是在导入者的作用域内对变量名的赋值语句,也就是变量名拷贝运算,而不是变量名的别名机制。例如:定义了下列模块(nested1.py):
,
如果在另一个模块内(nested2.py)使用from导入两个变量名,就会得到两个变量名的拷贝,而不是对两个变量名的连接。在导入者内部修改变量名,只会重设该变量名在本地作用域版本的绑定值,而不是nested1.py中的变量名:
。
然而,如果使用import获得了整个模块,然后赋值某个点号运算的变量名,就会修改nested1.py的变量名。点号运算是将python定向到了模块对象内的变量名,而不是导入者的变量名(nested3.py):
15、from×会让变量语义模糊:使用from module import×语句形式时,因为不会列出想要的变量,可能会意外覆盖了作用域内已使用的变量名。更遭的是,这将很难确认变量来自何处。如果有一个以上的被导入文件使用了from×形式,就更如此了。例如,如果在三个模块上使用from×,没有办法知道简单的函数调用真正含义,除非去搜索这三个外部的模块我呢间(三个可能都在其他目录内):
,
16、reload不会影响from导入:因为from在执行时会复制(赋值)变量名,所以不会连接到变量名的那个模块。通过from导入的变量名就简单的变成了对象的引用,当from运行时这个对象恰巧在被导入这内由相同的变量名引用。所以重载被导入者对于使用from导入模块变量名的客户端没有影响,也就是说,客户端的变量名依然引用了通过from取出的原始对象,即使之后原始模块中的变量名进行了重新设置:
,
为了保证重载更有效,可以使用import以及点号运算来取代from。点号运算会回到模块,这样就会找到模块重载后变量名的新的绑定值:
。
17、reload、from以及交互模式测试:第3章就说过,最好不要通过导入或重载来启动程序,因为会很复杂,特别是引入from之后,假设在一个交互模式中,通过from加载并测试模块:
,
发现一个bug,跳回编辑窗口,做修改,并试着重载模块:
,
但是这样行不通:from语句赋值的是变量名function,而不是module。要在reload中引用模块,得先通过import至少将其加载一次:
,
不过这样也还是没法运行:reload更新了模块对象,但是像上一节说的,function这样的变量名是之前就从模块中复制出来的,所以依然是引用了旧对象。要获得新的function,就必须在reload之后调用module.function。或者重新执行from:
。
所以使用reload和from本质上就有些问题:不但得记住导入后要重载,还得记住在重载后重新执行from语句。即使是专家,也够头疼的(在3.0中甚至变得更糟,因为必须记住导入reload本身)。
18、递归形式的from导入无法工作:如果使用import取出整个模块,模块的变量名在稍后使用点号运算,在获得值之前都不会读取。但是如果使用from来取出特定的变量名,必须记住,只能读取在模块中已经赋值了的变量名。例如下面有模块recur1和recur2。recur1给变量名X赋值,然后在赋值变量名Y之前导入recur2.这时,recur2可以用import把recur1整个取出(recur1已经存在于python内部的模块表中了)。但是如果使用from,就只能看见变量名X。变量名Y是在导入recur1后赋值的,现在不存在:
,
。
当recur1的语句由recur2递归导入时,python会避免重新执行(否则导入会让脚本变成死循环)。但是,被recur2导入时,recur1的命名空间还不完整。解决方法就是,不要在递归导入中使用from。如果这么做,虽然python不会卡在死循环中,但是却又会依赖于模块中语句的顺序:两种方法避开这个陷阱:a、小心设计:最大化模块的聚合性,同时最小化模块间的耦合性;b、如果无法断开循环,就是用import和点号运输,将模块变量名的读取放在后边,要么就是在函数中,或者在文件末尾附近去执行from(而不是模块顶层)。以延迟其执行。