、模块基础
1、基本概念
模块是最高级别的程序组织单元,它将程序代码和数据封装起来以便重用。从实际角度来看,模块往往对应于python程序文件(或是用外部语言如C、Java或C#编写而成的扩展)。每一个文件都是一个模块,并且模块导入其他模块之后就可以使用导入模块定义的变量名。模块可以由两个语句和一个重要的内置函数进行处理。
import: 使客户端(导入者)以一个整体获取一个模块;
from: 允许客户端从一个模块文件中获取特定的变量名;
imp.reload: 在不中止Python程序的情况下,提供了一种重新载入模块文件代码的方法;
模块和类实际上就是一个重要的命名空间。
2、如何组织一个程序
一般来讲,一个Python程序包括了多个含有Python语句的文本文件。程序是作为一个主体的、顶层的文件来构造的,配合有零个或多个支持的文件,在python中这些文件称作模块。
在python中,顶层文件(又称为脚本)包含了程序主要的控制流程:这就是你需要运行来启动应用的文件。模块文件就是工具的库,这些工具是用来收集顶层文件(或者其他可能的地方)使用的组织。顶层文件使用了在模块文件中定义的工具,而这些模块使用了其他模块所定义的工具。
在一个导入语句中的模块名起到两个作用:
- 识别加载的外部文档,但是它也会变成赋值给被载入模块的变量。
- 模块定义的对象也会在执行时创建,就在import执行时,import会一次允许在目标文件中的语句从而建立其中的内容。
3、import如何工作
在Pyhon中,导入并非只是把一个文件文本插入另一个文件而已。导入其实是运行时的运算,程序第一次导入指定文件时,会执行三个步骤:
1. 找到模块文件;
2. 编译成位码(需要时);
3.执行模块的代码来创建其所定义的对象;
注意:这三个步骤只在程序执行时,模块第一次导入时才会进行。在这之后,导入相同模块,会跳过这三个步骤,而只提取内存中已加载的模块对象。从技术上讲,Python把载入的模块存储到一个名为sys.module的表中,并在一次导入模块的开始检查该表。如果模块不存在,将会启动一个三个步骤的过程。
搜索:
首先,python必须查找到import语句所引用的模块文件。注意:上一节例子中的import语句所使用的文件名中没有a.py,也没有目录路径,只有import b,而不是import c:\dir1\b.py。事实上,只能列出简单名称,路径和后缀是可以省略掉的,因为Python使用了标准模块搜索路径来找出import语句所对应的模块文件。
编译(可选):
遍历模块搜索路径,找到符合import语句的源代码文件后,如果必要的话,Python接下来会将其编译成字节码。
python会检查文件的时间戳,如果发现字节码文件比源码文件旧(例如,如果你修改过源文件),就会在程序运行时自动更新生成字节码代码。另一方面,如果发现.pyc字节码文件不必对应的.py源代码文件旧,就会跳过源代码到字节码的编译步骤。此外,如果Python在搜索路径上只发现了字节码文件,而没有源代码,就会直接加载字节码(这意味着你可以把一个程序只作为字节码文件发布,而避免发生源代码)。换句话说,如果有可能是程序的启动提速,就会跳过编译步骤。
注意,当文件导入时,就会进行编译。因此,通常不会看见程序顶层文件的.pyc字节码文件,除非这个文件也被其他文件导入:只有被导入的文件才会在机器上留下.pyc。顶层文件的字节码实在内部使用后就被丢弃了;被导入文件的字节码则保存在文件中从而可以提高之后的导入速度。
顶层文件通常是设计成直接执行,而不是被导入的。使用特定的__name__属性和__main__可以设计一个文件即可作为程序的顶层文件,并同时扮演被导入的模块工具。这类文件既能执行也能导入。
运行:
import操作的最后步骤是执行模块的字节码。文件中所有语句会依次执行,从头到尾,而此步骤中任何对变量名的赋值运算,都会产生所得到的模块文件的属性。
4、模块搜索路径
在大多数情况下,可以依赖模块导入搜索路径的自动特性,完全不需要配置这些路径。不过,如果你想在用户间定义目录边界来导入文件,就需要知道搜索路径是如何运作的,并予以调整。概括地讲,Python的模块搜索路径是这些主要组件组合而成的结果。其中有些进行了预先定义,而其中有些你可以进行调整来告诉Python去哪里搜索。
- 程序的主目录
- PYTHONPATH目录
- 标准链接库目录
- 任何.pth文件的内容(如果存在的话)
最后,这四个组件组合起来就变成了sys.path。搜索路径的第一和第三元素是自动定义的,但是因为Python会从头到尾搜索这些组件组合的结果,第二和第四元素,就可以用于扩展路径,从而包含你自己的源代码目录。
Python已经导入的模块模块保存在一个内置的sys.modules字典中,以使可以记录那些已经导入了。实际上,如果想要看看已经导入了哪些模块,可以导入sys并打印list(sys.moudlues.keys())。
主目录:
Python首先会在主目录下搜索导入的文件。这一入口的含义与你如何运行代码相关。当你运行一个程序的时候,这个入口是包含程序的顶层脚本文件的目录。当在交互模式下工作时,这一入口就是你当前的目录。
因为这个目录总是被先搜索,如果程序完全位于单一目录,所有导入都会自动工作,而不需要配置路径。另一方面,由于这个路径是先搜索的,其文件也将覆盖路径上的其他目录中具有相同名称的模块。如果你需要在自己的程序中使用库模块,小心不要以这种方式意外地隐藏库模块。
PYTHONPATH目录
之后,python会从左至右搜索PYTHONPATH环境变量设置中罗列出来的所有目录。简而言之,PYTHONPATH是设置包含python程序文件的目录的列表,这些目录可以是用户定义的或平台特定的目录名。你可以把想导入的目录都加进来,而python会使用你的设置来扩展模块搜索的路径。
因为python会先搜索主目录,当导入的文件跨目录时,这个设置才显得格外重要。也就是说,如果你需要被导入的文件与进行导入的文件处在不同的目录时。一旦你开始编写大量程序时,你可能想要设置PYTHONPATH变量,但是刚开始编写时,只要把所有模块文件放在交互模块使用的目录下就行了(也就是说,主目录),如此一来,导入都可运作,而你不需要去担心如何进行这个设置。
参考文章:
PYTHONPATH 是什么_氵文大师的博客-CSDN博客
python里的PYTHONPATH是干什么用的?_子燕若水的博客-CSDN博客
https://www.cnblogs.com/lifeofershisui/p/8135702.html
标准库目录
接着,Python会自动搜索标准库模块安装在机器上的那些目录。因为这些一定会被搜索,通常是不需要添加到PYTHONPATH之中的或包含到路径文件(接下来介绍)中的。
.pth文件目录
最后,python有一个相当新的功能,允许用户把有效的目录添加到模块搜索路径中去,也就是在后缀名为.pth(路径的意思)的文本文件中一行一行地列出目录。这些路径配置文件是和安装相关的高级功能,它提供了PYTHONPATH设置的一种替代方案。
5、配置搜索路线
所有这些的直接效果是,搜索路径的PYTHONPATH和路径文件部分允许我们调整导入查找文件的地方。设置环境变量的方法以及存储路径文件的位置,随着每种平台而变化,例如,在windows上,我们可能使用控制面板的系统图标来把PYTHONPATH设置为分号隔开的一串目录:
c:\pycode\utilities;d:\pycode\package1
或者,可以创建一个名为C:\Python30\pydirs.pth的文本文件,其内容如下所示:
c:\pycode\utilities
d:\pycode\package1
这些设置在其他平台也是类似的,但是细节可能有很大变化。
6、sys.path列表
如果你想看看模块的搜索路径在机器上的实际配置,可以通过打印内置的sys.path列表来查看这个路径。导入时,python会由左至右搜索这个列表中的每个目录。
通过修改sys.path这个列表,你可以修改将来的导入的搜索路径。然而,这种修改只会在脚本存在期间保持而已。PYTHONPATH和.path文件提供了更持久的路径修改方法。
7、模块文件选择
注意,文件名的后缀(例如,.py)是刻意从import语句中省略的。python会选择在搜索路径中第一个符合导入文件名的文件。例如, import b形式的import叙述可能会加载:
源代码文件b.py
字节码文件b.pyc
目录b,包导入(在第23章说明)
编译扩展模块(通常用C或C++编写),导入时使用动态连接(例如,Linux的b.so以及Cygwin和windows的b.dll或b.pyd)
用C编写的编译好的内置模块,并通过静态连接至Python
ZIP文件组件,导入是会自动解压缩
内存内映像,对于frozen可执行文件
Java类,在Jython版本的Python中
.NET组件,在IronPython版本的Python中
二、模块代码编写基础
1、模块的创建
当一个模块被导入时,python会把内部模块名映射到外部文件名,也就是通过把模块搜索路径中的目录路径加在前边,而.py或其他后缀名添加在后边。例如,名为M的模块最后会映射到某个包含模块程序代码的外部文件:<directory>\M.<extension>。
2、模块的使用
import语句:变量名module1有两个不同目的:识别要被加载的外部文件,同时会生成脚本中的变量,在文件加载后,用来引用模块对象。
import module1
module1.print('hello world')
因为import使一个变量名引用整个模块对象,我们必须通过模块对象来得到该模块的属性。
from语句:因为from语句会把变量名复制到另一个作用域,所以它就可以让我们直接在脚本中使用复制后的变量名,而不需要通过模块。
from module1 import printer
printer('hello world')
from语句出现时,导入的变量名会复制到作用域内,在脚本中使用该变量名就可以少输入一些:我们可以直接使用变量名,而无须在嵌套模块名称之后。
from语句其实只是稍微扩展了import语句而已。它照常导入了模块文件,但是多了一个步骤,将文件中的一个或多个变量名从文件中复制出来。需要注意的是from语句没有一个变量名来引用整个模块对象了。
from *语句:当我们使用*时,会取得模块顶层所有赋了值的变量名的拷贝。
from module1 import *
printer('hello world')
注意:在python3.0中,from *语句形式只能用在一个模块文件的顶部,不能用于一个函数中。
3、导入只发生一次
模块会在第一次import或from时载入并执行,并且只在第一次如此。这是有意为之的,因为该操作开销较大。在默认的情况下,python只对每个文件的每个进程做一次操作。之后的导入操作都只会取出已加载的模块对象。
- import和from是赋值语句
就像def一样,import或from是可执行的语句,而不是编译期间的声明,而且他们可以嵌套在if测试中,出现在函数def之中等,直到执行程序时,python执行到这些语句,才会进行解析。换句话说,被导入的模块和变量名,直到他们所对应的import或from语句执行时,才可以使用。就像def一样,import和from都是隐形的赋值语句。
- import将整个模块对象赋值给一个变量名
- from将一个或多个变量名赋值给另一个模块中同名的对象
关于赋值语句方面的内容,也使用于模块的读取。例如,以from复制的变量名会变成对其共享对象的引用。就像函数的参数,对已取出的变量名重新赋值,对于其复制之处的模块并没有影响。但是修改一个已取出的可变对象,则会影响导入的模块内的对象。
x=1
y=[1, 2]
%python
>>>from small import x, y
>>>x= 42
>>>y[0]=42
此处,x并不是一个共享的可变对象,但y是。导入者中的变量名y和被导入者都引用相同的列表对象,所以在其中一个地方的修改,也会影响另一个地方的这个对象。
>>>import small
>>>small.x
1
>>>small.y
[42, 2]
- 文件间变量名的改变
在交互会话模式下对x的赋值运算,只会修改该作用域内的变量x,而不是这个文件内的x。以from复制而来的变量名和其来源的文件之间并没有联系。为了实际修改另一个文件中的全局变量名,必须使用import。
%python
>>>from small import x, y #copy two names out
>>>x = 42 #changes my x only
>>>import small #get module name
>>>small.x = 42 #changes x in other module
- import和from的对等性
注意:在上一个例子中,我们需要在from后执行import语句,来获取small模块的变量名。from只是把变量名从一个模块复制到另一个模块,并不会对模块名本身进行赋值。至少从概念上来说,一个这样的form语句:
from module import name1, name2 #copy these two names out (only)
与下面这些语句是等效的:
import module
name1 = module.name1
name2 = module.name2
del module
注意:from的第一步骤也是普通的导入操作。因此,from总是会把整个模块导入到内存中(如果还没导入的话),无论是从这个文件中复制出来多个变量名。只加载模块文件的一部分(例如,一个函数)是不可能的,但是因为模块在python职中是字节码而不是机器码。通常可以忽略效率的问题。
- from语句潜在的陷阱
from语句有破坏命名空间的潜质,这是真的,至少从理论上讲是这样的。如果使用from导入变量,而那些变量恰巧和作用域中现有的变量同名,变量就会悄悄地被覆盖掉。使用import就不存在这种问题。
另一方面,和reload调用同时使用时,from语句有比较严重的问题。(暂时用不到就不研究了)
- 何时使用import
当你必须使用两个不同模块内定义的相同变量时,才真的必须使用import,这种情况下不能使用from。
4、模块命名空间
模块最好理解为变量名的封装,也就是定义想让系统其余部分看见变量名的场所。从技术上讲,模块通常相应于文件,而python会建立模块对象,以包含模块文件内所赋值的所有变量名。但是,简而言之,模块就是命名空间(变量名建立所在的场所),而存在于模块之内的变量名就是模块对象的属性。
- 文件生成命名空间
- 模块语句会在首次导入时执行。系统中,模块在第一次导入时无论在什么地方,python都会建立空的模块对象,并追一执行该模块文件内的语句,依照文件从头到尾的顺序。
- 顶层的赋值语句会创建模块的属性。在导入时,文件顶层(不在def或class之内)赋值变量的语句(例如,=和def),会建立模块对象的属性,赋值的变量名会存储在模块的命名空间内。
- 模块的命名空间能通过属性__dict__或dir(M)获取。由导入而建立的模块的命名空间是字典;可通过模块模块对象相关联的内置__dict__属性来读取,而且能通过dir函数查看。dir函数大致与对象的__dict__属性的键排序后的列表相等,但是它还包含了类继承的变量名。
- 模块是一个独立的作用域(本地变量就是全局变量)。正如上一节所显示的,模块顶层变量名遵循和函数内变量名相同的引用/赋值规则。
三、模块包
除了模块名之外,导入也可以是指定目录路径。Python代码的目录就称为包。因此,这类导入就称为包导入。事实上,包导入是把计算机上的目录变成另一个python命名空间,而属性则对应于目录中所包含的子目录和模块文件。
对于组织大型系统内的文件会很方便,而且可以简化模块搜索路径的设置。我们将知道,当多个相同名称的程序文件安装在某一个机器上时,包导入也可以偶尔用来解决导入的不确定性。
1、包导入基础
那么。包导入时如何运作的呢?在import语句中列举简单文件名的地方,可以改成列出路径的名称,彼此以点号相隔。
import dir1.dir2.mod
from语句也是一样的:
from dir1.dir2.mod import *
这些语句中的”点号“路径是对应于机器上目录层次的路径,通过这个路径可以获得到文件mod.py(或类似文件,扩展名可能会变化)。也就是说,上面的语句是表明了机器上有个目录dir1,而dir1里面有个子目录dir2,而dir2内包含有一个名为mod.py(或类似文件)的模块文件。
此外,这些导入意味着,dir1位在某个容器目录dir0中,这个目录可以在python模块搜索路径中找到。换句话说,这两个import语句代表了这样的目录结构。
dir0\dir1\dir2\mod.py
容器目录dir0需要添加在模块搜索路径中(除非这是顶层文件的主目录),就好像dir1是模块文件那样。
一般地,包导入路径中最左边的部分仍然是相对于我们之前讲的sys.path模块搜索路径列表中的一个目录。从此以后,脚本内的import语句明确指出找到模块的目录路径。
2、包和搜索路径设置
如果要使用这个特性,要记住,import语句中的目录路径只能以点号间隔的变量。选择点号语法,一部分是考虑到跨平台,但也是因为import语句中的路径会变成实际的嵌套对象路径。这种语法意味着,如果你忘了在import中省略.py,就会得到奇怪的错误信息。例如,import mod.py会被看出看成目录路径导入:这是要载入mod.py,但解释器却试着载入mod\py.py,而最终就是发出可能令人困惑的错误信息。
3、__init__.py包文件
如果选择使用包导入,就必须多遵循一条约束:包导入语句的路径中的每个目录都必须有__init__.py这个文件,否则导入会失败。也就是说,在我们所采用的例子中,dir1和dir2内都必须包含__init__这个文件。容器目录dir0不需要这类文件(如果有的话也会被忽略),因为其本身没有列在import语句之中。
更正式地说,像下面这样的目录结构
dir0\dir1\dir2\mod.py
以及这种形式的import语句:
import dir1.dir2.mod
必须遵循一列规则:
- dir1和dir2中必须都含有一个__init__.py文件;
- dir0是容器,不需要__init__.py文件;如果有的话,这个文件也会被忽略;
- dir0(而非dir0\dir1)必须列在模块搜索路径上(也就是此目录必须是主目录,或列在PYTHONPATH之中)
更通常的情况下,__init__文件扮演了包初始化的钩子:替目录产生模块命名空间以及使用目录导入时实现from *行为的角色。
- 包的初始化
python首次导入某个目录时,会自动执行该目录下__init__.py文件中的所有程序代码。因此,这类文件自然就是放置包内文件所需要初始化的代码的场所。例如,包可以使用其初始化文件,来创建所需要的数据文件、连接数据库等。一般而言,如果直接执行,__init__.py文件没什么用,当包首次读取时,就会自动执行。
- 模块命名空间的初始化
在包导入的模型中,脚本内的目录路径,在导入后会变成真实的嵌套对象路径。例如,上个例子中,导入后,表达式dir1.dir2会运行并返回一个模块对象,而此对象的命名空间包含了dir2的__init__.py文件所赋值的所有变量名。这类文件为目录(实际没有相配的模块文件)所创建的模块对象提供了命名空间。
- from *语句的行为
作为一个高级功能,你可以在__init__.py文件内使用__all__列表来定义目录以from *语句形式导入时,需要导出什么。在__init__.py文件中,__all__列表是指当包(目录)名称使用from *的时候,应该导入的子模块的名称清单。如果没有设定__all__,from *语句不会自动加载嵌套于该目录内的子模块。取而代之的是,只导入该目录的__init__.py文件中赋值语句定义的变量名,包括该文件中程序代码明确导入的任何子模块。例如,某目录中__init__.py内的语句from submodule import X,会让变量名可在该目录的命名空间内使用。
4、包导入实例
让我们实际编写刚才所谈的例子,来说明初始化文件和路径是如何运作的吧。下列三个文件分别位于dir1和dir1的子目录dir2中:
#dir1\__init__.py
print('dir1 init')
x = 1
#dir2\__init__.py
print('dir2 init')
y = 2
#dir3\__init__.py
print('dir3 init')
z = 3
这里dir1要么是在我们的工作所在目录(也就是主目录)的子目录,要么就是位于模块搜索路径中(技术上就是sys.path)的一个目录的子目录。无论哪一种,dir1的容器都不需要__init__.py文件。
当python向下搜索路径时,import语句会在每个目录首次遍历时,执行该目录的初始化文件。print语句加在这里用来跟踪他们的执行。
%python
>>>import dir1.dir2.mod
dir1 init
dir2 init
in mod.py
导入后,import语句内的路径会变成脚本的嵌套对象路径。在这里,mod是对象,嵌套在对象dir2中,而dir2又嵌套在对象dir1中。
>>>dir1
<module 'dir1' from 'dir1\__init__.pyc'>
>>>dir1.dir2
<module 'dir1.dir2' from 'dir1\dir2\__init__.pyc'>
>>>dir1.dir2.mod
<module 'dir1.dir2.mod' from 'dir1\dir2\mod.pyc'>
实际上,路径中的每个目录名称都会变成赋值了模块对象的变量,而模块对象的命名空间则是由该目录内的__init__.py文件中所有赋值语句进行初始化的。dir1.x引用了变量x, x是在dir1\__init__.py中赋值的,而mod.z引用的变量z则是在mod.py内赋值的。
>>>dir1.x
1
>>>dir1.dir2.y
2
>>>dir1.dir2.mod.z
3
5、包对应的from语句和import语句
import语句和包一起使用时,有些不方便,因为你必须经常在程序中重新输入路径。例如,上一小节例子中,每次要得到z时,就得从dir1开始重新输入完整路径,并且每次都要重新执行整个路径。如果你想要尝试直接读取dir2或者mod,就会得到一个错误:
>>>dir2.mod
NameError: name 'dir2' is not defined
>>>mod.z
NameError: name 'mod' is not defined
因此,让包使用from语句,来避免每次读取时都得重新输入路径,通常这样比较方便。也许更重要的是,如果你重新改变目录树结构,from语句只需在程序代码中更新一次路径,而import则需要修改很多地方。import作为一个扩展功能(下一章讨论),在这里也有一定的帮助,它提供一个完整路径较短的同义词:
%python
>>>from dir1.dir2 import mod
dir1 init
dir2 init
in mod.py
>>>mod.z
3
>>>from dir1.dir2.mod import z
>>>z
3
>>>import dir1.dir2.mod as mod
>>>mod.z
3
6、包相对导入
目前为止,对包导入的介绍主要集中在从包的外部导入包文件。在包自身的内部,包文件的导入可以使用和外部导入相同的路径语法。但是,它们也可能使用特殊的包内搜索规则来简化导入语句。也就是说,包内的导入可能相对于包,而不是列出包导入路径。
如今,这种工作与版本有关。python2.6首先在导入上隐式地搜索包目录,而python3需要显式地相对导入语法。
- 相对导入基础知识
在python3.0和python2.6中,from语句现在可以使用前面的点号(".")来指定,它们需要位于同一个包中的模块(所谓的包相对导入),而不是位于模块导入搜索路径上某处的模块(叫做绝对导入),也就是说:
- python3.0和python2.6中,我们可以使用from语句前面的点号来表示,导入应该相对于外围的包——这样的导入将只是在包的内部搜索,不会搜索位于导入搜索路径(sys.path)上某处的同名模块。直接效果是包模块覆盖了外部的模块。
- 在python2.6中,包的代码中的常规导入(没有前面的点号),目前默认为一种先相对再绝对的搜索路径顺序,也就是说,它们首先搜索包自己的路径。然而,在python3.0中,在一个包中导入默认是绝对的——在缺少任何特殊的点语法的时候,导入忽略了包含自身并在sys.path搜索路径上的某处查找。
7、为什么使用相对导入
这个功能的设计初衷是,当脚本在同名文件出现在模块搜索路径上许多地方时,可以解决模糊性。考虑如下包的目录:
mypkg\
__init__.py
main.py
string.py
这定义了一个名为mypkg的包,其中还有名为mypkg.main和mypkg.string的模块。现在,假设模块main试图导入名为string的模块。在python2.6和稍早的版本中,python会先寻找mypkg目录以执行相对导入。这会找到并导入位于该处的string.py文件,将其赋值给mypkg.main模块命名空间内的变量名string。
不过,import的愿意可能是要导入python标准库的string模块。可惜的是,在这些python版本中,没有办法直接忽略mypkg.string而去寻找位于模块搜索路径更右侧的标准库中的string模块。此外,我们无法使用包导入路径来解决这个问题,因为我们无法依赖在每台机器上都存在标准链接库以上的额外包的目录结构。
换句话说,包中的导入可能是模糊的。在包内,import spam语句是指包内或包外的模块,这不确定。更准确地讲,一个局部的模块或包可能会隐藏sys.path上的另一个模块,无论是有意无意的。
实际上,python用户可以避免为他们自己的模块重复使用标准库模块的名称(如果需要标准string,就不要把新的模块命名为string)。但是,这样对包是否会不小心隐藏标准模块没什么帮助。再者,python以后可能新增标准库模块,而其名称刚好和自己的一个模块同名。依赖相对导入的程序代码比较不容易理解,因为读者对于期望使用哪个模块,可能会感到困惑。如果程序代码中能明确地进行分辨,就比较好了。
- python3.0中相对导入解决方案
要解决这个问题,在包中运行的导入已经在python3.0改为绝对的。在这种方式下,我们示例文件mypkg/main.py中,一条如下形式的import语句将总是在包之外找到一个string,通过sys.path的绝对导入搜索。
import string
没有前面的点号的一条from语句,也是看作绝对的。
from string import name
如果你真的想要从包中导入一个模块,而没有给出从包根目录的完整路径,在from语句中使用点语法仍然可能做到相对导入。
from . import string #import mypkg.string(releative)
我们也可以使用相对语法从一个模块复制特定的名称。
from .string import name1, name2
实际上,相对导入中的“.”用来表示包含文件的包目录,而导入就出现在该文件中。前面再加一个点,将执行从当前包的父目录的相对导入。例如,下面的语句
from .. import spam
将会导入mypkg的一个兄弟,位于该包自己的包含目录中的spam模块,该模块紧挨着mypkg。
- 相对导入语法中连续两个点".."表示当前程序文件所属包的父目录;
- 相对导入语法中连续三个点"..."表示当前程序文件所属包的父目录的父目录;
- 相对导入语法中连续四个点"...."表示当前程序文件所属包的父目录的父目录的父目录;
- 以此类推,每多加一个点,就代表多往上追加一层目录。
- 相对导入VS绝对包路径
此外,一个文件有时候也可以在一条绝对导入语句中显式地指定其包。例如,在下面语句中,将在sys.path的一个绝对路径中找到mypkg。
from mypkg import string
然而,这依赖于配置以及模块搜索路径设置的顺序,尽管相对导入的点语法不会依赖于此。实际上,这种形式需要直接包含将要包含在模块搜索路径中的mypkg。通常,像这样显式地指定包的时候,绝对导入语句必须列出包的根目录下的所有目录。
from system.section.mypkg import string
在较大或较深的包中,这可能比点语法要做更多的工作。
from . import string
8、相对导入的作用域
相对导入可能有些令人困惑。但是,如果你记住一些关键点,会很有帮助:
- 相对导入适用于只在包内导入。记住,这种功能的模块搜索路径修改只应用于位于包内的模块文件中的import语句。位于包文件之外的通常的导入将像前面所介绍的那样工作,首先自动搜索包含了顶级脚本的目录。
- 相对导入只是用于from语句。还要记住,这一功能的新的语法只是用于from语句,而不适用于import语句。可以这样检测它,一个from中的模块名前面有一个或多个点号。包含点号但前面没有一个点号的模块名是包导入,而不是相对导入。
- 术语含糊不清。坦率地讲,用来描述这一功能的术语可能太过令人混淆了。实际上,所有的导入对于某些事物来说都是相对的。在一个包外部,导入仍然是相对于sys.path模块搜索路径上列出的目录的。
9、模块查找规则总结
使用包导入和相对导入,python3.0中的模块查找可以完整地概括为如下几条:
- 简单模块名(例如,A)通过搜索sys.path路径列表上的每个目录来查找,从左至右进行。这个列表由系统默认设置和用户配置设置而成。
- 包是带有一个特殊的__init__.py文件的python模块的直接目录,这使得一个导入中可以使用A.B.C目录路径语法。在A.B.C的一条语句中,名为A的目录位于相对于sys.path的常规模块导入搜索,B是A中的另一个包子目录,C是一个模块或者B中的其他可导入项;
- 在一个包文件中,常规的import语句使用和其他地方的导入一样的sys.path搜索规则。包中的导入使用from语句以及前面的点号,然而,他是相对于包的,也就是说,只检查包目录,并且不使用常规的sys.path查找。
10、相对导入的应用
理论介绍足够了,让我们来看一些快速测试,以展示相对导入背后的概念。
- 在包之外导入
首先,正如前面所提到的,这一功能不会影响一个包之外的导入。因此,如下的代码像预期的那样查找标准库string模块。
但是,如果在所工作的目录中添加一个同名的模块,将会选择该模块,因为模块搜索路径的第一条是当前工作目录(CWD)。
换句话说,常规的导入仍然相对于“主”目录(顶级的脚步的包含目录,或者我们正则工作的目录)。实际上,如果不是在用作一个包的部分的一个文件中,甚至不允许相对导入语法。
因为顶级脚本文件执行时,
相对路径运行注意事项:
- 在没有明确指定包结构的情况下,Python 是根据 __name__ 来决定一个模块在包中的结构的,如果是 __main__ 则它本身是顶层模块,没有包结构,如果是base.demo01.demo02 结构,那么顶层模块是 base。
- 如果是相对导入,一个模块必须有包结构且只能导入它的顶层模块内部的模块
- 如果一个模块被直接运行,则它自己为顶层模块,不存在层次结构,所以找不到其他的相对路径。
在本节中的这个以及所有示例中,在交互提示模式中输入的代码与将其放在一个顶层脚本中运行的行为是相同的,因为sys.path上的第一个条目要么是交互的工作目录,要么是包含顶层文件的目录。唯一的区别是,sys.path的开始是一个绝对路径,而不是一个空字符串。
- 包内的导入
现在,让我们来去除 在CWD中编写的本地string模块,并构建带有两个模块的一个包目录,包括必须空的test\pkg\__init__.py文件(在这里省略了)。
这个包中的第一个文件试图用一条常规的import语句导入第二个文件。由于这在python2.6中当作相对的,而在python3.0中当作绝对的,因此,后者中将会失败。也就是说,python2.6首先搜索包含的包,但是Python3.0不这么做。这是在python3.0中必须注意的非兼容行为。
为了使得这在python2.6和python3.0下都有效,使用特殊的相对导入语法来修改第一个文件,以便其导入在python3.0中也搜索包目录。
- 导入仍然是相对于CWD的
注意,在前面的示例中,包模块仍然必须访问string这样的标快库模块。实际上,它们的导入仍然相对于模块搜索路径上的条目,即便这些条目自身是相对的。如果我们再次向CWD添加了一个string模块,包中的导入将会在那里找到它,而不是在标准库中。尽管我们可以在python3.0中略过带有一个绝对导入的包目录,我们仍然不能略过导入包的程序的主目录。
- 使用相对导入和绝对导入选择模块
为了展示这如何应用于标准模块库的导入,再一次重新设置包。去掉本地的string模块,并在包自身之中定义一个新的。
现在,获得哪个版本的string模块取决于你使用哪个python版本。和前面一样,python3.0把第一个文件中的导入解释为绝对的并略过了该包,但python2.6不会这么做。
在python3.0中使用相对导入语法会迫使再次搜索包,就好像在python2.6中一样——通过在python3.0中使用绝对和相对导入语法,我们可以显式地跳过或选择包目录。实际上,这是python3.0方式所解决的情况。
相对导入语法实际上是一种绑定声明,而不是一种偏好,注意到这点很重要。如果我们删除了这个例子中的string.py文件,string.py中的相对导入在python3.0和python2.6中都会失败,而不是回归到这一模块(或任何其他模块)的标准库版本。
相对导入所引用的模块必须在包目录中存在。
- 导入仍然是相对于CWD的
尽管绝对导入允许我们略过包模块,它们仍然依赖于sys.path上的其他部分。为了最后进行一次测试,让我们定义自己的两个string模块。在下面的代码中,CWD中有一个为该名称的模块,包中有一个该名称的模块,并且,另一个string模块位于标准库中。
当我们使用相对导入语法导入string模块时,我们如愿地得到了包中的那个版本。
使用绝对语法时,我们得到了不同的版本。python2.6将这条语句解释为相对于包的,但python3.0将其看作绝对的。在这种情况下,真的意味着它略过包并载入相对于CWD的版本(而不是相对于标准库的版本)。
你可以看到,尽管包显式地要求目录中的模块,它们的导入仍然是相对于常规模块搜索路径的剩余部分。在这个例子中,程序中的一个文件使用的包隐藏了该包可能想要的标准库模块。在python3.0中真正发生的变化只是允许包代码在包的内部或外部选择文件。由于导入方法可能依赖于无法预见的一个封闭环境,python3.0中的绝对导入并不能保证找到标准库中的模块。
四、高级模块话题
1、最小化from *的破坏:_X和__all__
有种特定的情况,把下划线放在变量名的前面(例如,_X),可以防止客户端使用from *语句导入模块名时,把其中的那些变量名复制出去。这其实是为了对命名空间的破坏最小化而已。因为from *会把所有变量名复制出去,导入者可能得到超出它所需要的部分(包括会覆盖导入者内的变量名的变量名)。下划线不是“私有”声明:你还是可以使用其他导入形式看见并修改这类变量名。例如,使用import语句。
此外,也可以在模块顶层把变量名的字符串列表赋值给变量__all__,以达到类似于_X命名惯例的隐藏效果。例如:
__all__ = ["Error", "encode", "decode"] #expose these only
使用此功能时,from *语句只会把列在__all__列表中的这些变量名复制出来。事实上,这和_X惯例相反:__all__是指出要复制出来的变量名,而_X是指出不被复制出来的变量名。Python会先寻找模块内的__all__列表;如果没有定义的话,from *就会复制出开头没有单下划线的所有变量名。
就像_X惯例一样,__all__列表只对from *语句这种形式有效,它并不是私有声明。
2、混合用法模式:__name__和__main__
这是一个特殊的与模块相关的技巧,可把文件作为成模块导入,并以独立式程序的形式运行。每个模块都有个名为__name__的内置属性,python会自动设置该属性。
- 如果文件是以顶层程序文件执行,在启动时,__name__就会设置为字符串“__main__”;
- 如果文件被导入,__name__就会被设成客户端所了解的模块名;
结果就是模块可以检测自己的__name__,来确定它是在执行还是在导入。
实际上,一个模块的__name__变量充当一个使用模式标志,允许它编写成一个可导入的库和一个顶层脚本。尽管简单,我们将会看到这一钩子几乎在可能遇到的每个python程序文件中应用。
以__name__进行单元测试
略
3、修改模块搜索路径
模块搜索路径是一个目录列表,可以通过环境变量PYTHONPATH以及可能的.pth路径文件进行定制。还没有介绍的是,实际上python程序本身是如何修改搜索路径的,也就是修改名为sys.path的内置列表。sys.path在程序启动时就会进行初始化,但在那之后,可以随时对其元素进行删除,附加和重设。
%python
>>>sys.path.append('C:\\sourcedir')
一旦做了这类修改,就会对python程序中将要导入的地方产生影响,因为所有导入和文件都共享了同一个sys.path列表。事实上,这个列表可以任意修改。
>>>sys.path = [r'd:\temp']
>>>sys.path.append('c:\\lp4e\\example')
>>>import string
ImportError:No module named string
注意,sys.path的设置方法只在修改的python会话或程序(即进程)中才会存续。在python结束后,不会保留下来。PYTHONPATH和.pth文件路径配置是保存在操作系统中,而不是执行中的python程序。因此使用这种配置方法更全局一些:机器上的每个程序都会去查找PYTHONPATH和.pth,而且在程序结束后,它们还存在着。
4、import语句和from语句的as扩展
import和from语句都可以扩展,让模块可以在脚本中给予不同的变量名。
import modulename as name
相当于
import modulename
name = modulename
del modulename #don't keep original name
在这类import之后,可以(事实上是必须)使用as之后的变量名来引用该模块。
from语句也可以这么用,把从某个文件导入的变量名,赋值给脚本中不同的变量名:
from modulename import attrname as name
这个扩展很有用,替变量名较长的变量提供简短一些的同义词,而且当已在脚本中使用一个变量名使得执行普通import语句会被覆盖时,使用as,就可避免变量名的冲突:
import reallylongmodulename as name #use shorted nickname
name.func()
from module1 import utility as util1 #can have only 1 "utility"
from module2 impory utility as util2
util1()
util2()
此外,使用之前提到的包导入功能时,也可以为整个目录提供简短,简单的名称,是否方便
import dir1.dir2.mod as mod
mod.func()
5、用名称字符串导入模块
一条import或from语句中的模块名是直接编写的变量名称。然而,有时候,我们的程序可以在运行时以一个字符串的形式获取想要导入的模块名称(例如,如果一个用户从一个GUI中选择一个模块名称)。遗憾的是,我们无法使用import语句来直接载入以字符串形式给出其名称的一个模块,python期待一个变量名称,而不是字符串。例如:
>>>import "string"
SyntaxError: invalid syntax
直接把该字符串赋给一个变量名称也是无效的:
x = "string"
import x
这里,python将会尝试导入一个文件x.py,而不是string模块——一条import语句中的名称即变成了赋给载入的模块的一个变量,也从字面上标识了该外部文件。
为了解决这个问题,我们需要使用特殊的工具,从运行时生成的一个字符串来动态载入一个模块。最通用的方法是,把一条导入语句构建为python代码的一个字符串,并且将其传递给exec内置函数以运行(exec是python2.6中的一条语句,但是,它完全可以像这里展示的那样使用——直接省略掉圆括号):
>>>module = "string"
>>>exec("import " + module) #run a string fo code
>>>string #imported in this namespace
<module 'string' from 'C:\python30\lib\string.py'>
exec函数(及其近亲eval)编译一个代码字符串,并且将其传递给python解释器以执行。在python中,字节代码编译器在运行时可以使用。因此,我们像这样编写构建和运行其他程序的程序。默认情况下,exec运行当前作用域中的代码,但是,你可以通过传入可选的命名空间字典来更加具体地应用。
6、模块陷阱
- 顶层代码的语句次序的重要性
当模块首次导入(或重载)时,python会从头到尾执行语句。这里有些和前句引用(forward reference)相关的含义,指的强调下:
- 在导入时,模块文件顶层的程序代码(不在函数内)一旦python运行时,就会立即执行。因此该语句无法引用文件后面位置赋值的变量名。
- 位于函数主体内的代码直到函数被调用后才会运行。因为函数内的变量名在函数实际执行前都不会解析,通常可以引用文件内任意地方的变量。
在顶层程序内混用def不仅难读,也造成了对语句顺序的依赖性。作为一条原则,如果需要把立即执行的代码和def一起混用,就要把def放在文件前面,把顶层代码放在后面。这样的话,你的函数在使用的代码运行的时候,可以保证它们都已定义并赋值过了。
- from复制变量名,而不是连接
尽管常用,但from语句也是python种各种潜在陷阱的源头。from语句其实是在导入者的作用域内对变量名的赋值语句,也就是变量名拷贝运算,而不是变量名的别名机制。他的实现和python所有赋值运算都一样,但是其微妙之处在于,共享对象的代码存在于不同的文件中。例如,假设我们定义了下列模块(nested1.py)。
如果我们在另一个模块内(nested2.py)使用from导入两个变量名,就会得到两个变量名的拷贝,而不是对这两个变量名的连接。在导入者内修改变量名,只会重设该变量名在本地作用域版本的绑定值,而不是nested1.py中的变量名:
然而,如果我们使用import获得了整个模块,然后赋值某个点号运算的变量名,就会修改nested1.py中的变量名。点号运算把python定向到了模块对象内的变量名,而不是导入者的变量名(nested3.py)。