【Python学习手册(第四版)】学习笔记23-模块包

个人总结难免疏漏,请多包涵。更多内容请查看原文。本文以及学习笔记系列仅用于个人学习、研究交流。

本文主要介绍模块包,介绍了包导入(基础、__init__.py包文件)、包绝对导入(import string/from dr1 import xxx)、相对导入(from .dr2 import xxx),以及案例,分享了一些小技巧。本文较简单,理解性内容较多但不复杂,对于Python2.6部分内容可忽略。

包不仅让导入在较大系统中更有意义,也可以简化了导入搜索路径设置(如果所有跨目录的导入都在共同根目录下的话),而且当同名的模块有一个以上时,也可解决模糊性(通过包导入所引入的模块所在目录名称来区分)。


目录

模块包

包导入基础

包和搜索路径设置

__init__.py包文件

包的初始化

模块命名空间的初始化

from*语句的行为

包导入实例

包对应的from语句和import语句

为什么要使用包导入

举例三个系统

包相对导入

基础知识

为什么使用相对导入

相对导入解决方案

相对导入VS绝对包路径

相对导入的作用域

模块查找规则总结

相对导入应用

在包之外导入

包内的导入

使用相对导入和绝对导入选择模块

为什么要在意:模块包

技巧

每次引用包的内容时,如何避免重复包的完整路径?

在什么情况下必须通过import而不能通过from使用包?


模块包

除了模块名之外,导入也可以指定目录路径。Python代码的目录就称为包,因此,这类导入就称为包导入。事实上,包导入是把计算机上的目录变成另一个Python命名空间,而属性则对应于目录中所包含的子目录和模块文件。

这是有点高级的特性,但是它所提供的层次,对于组织大型系统内的文件会很方便,而且可以简化模块搜索路径的设置。我们将知道,当多个相同名称的程序文件安装在某一机器上时,包导入也可以偶尔用来解决导入的不确定性。

由于它只与包中的代码相关,在这里还将介绍Python的相对导入模块和语法。正如后面将看到的,这种方法修改了搜索路径并且扩展了在包中用于导入的from语句。

包导入基础

包导入是如何运作的呢?在import语句中列举简单文件名的地方,可以改成列出路径的名称,彼此以点号相隔。

from语句也是一样的:

这些语句中的“点号”路径是对应于机器上目录层次的路径,通过这个路径可以获得到文件mod.py(或类似文件,扩展名可能会有变化)。也就是说,上面的语句是表明了机器上有个目录dir1,而dir1里有子目录dir2,而dir2内包含有一个名为mod.py(或类似文件)的模块文件。

此外,这些导入意味着,dir1位在某个容器目录dir0中,这个目录可以在Python模块搜索路径中找到。换句话说,这两个import语句代表了这样的目录结构(以DOS反斜线分隔字符显示)。

容器目录dir0需要添加在模块搜索路径中(除非这是顶层文件的主目录),就好像dir1是模块文件那样。

一般地,包导入路径中最左边的部分仍然是相对于在之前所介绍的sys.path模块搜索路径列表中的一个目录。从此以后,脚本内的import语句明确指出找到模块的目录路径。

包和搜索路径设置

如果使用这个特性,要记住,import语句中的目录路径只能是以点号间隔的变量

不能在import语句中使用任何平台特定的路径语法。例如,C:\dir1、My Documents.dir2或../dir1:这些从语法上讲是行不通的。所需要做的就是,在模块搜索路径设置中使用平台特定的语法,来定义容器的目录。

例如,上一个例子中,dir0(加在模块搜索路径中的目录名)可以是任意长度而且是与平台相关的目录路径,在其下能够找到dir1。而不是使用像这样的无效的语句。

增加C:\mycode在PYTHONPATH系统变量中或是.pth文件中(假设它不是这个程序的主目录,这样的话就不需要这个步骤了),并且这样描述。

实际上,模块搜索路径上的项目提供了平台特定的目录路径前缀,之后再在import的那些路径左边添加了这些路径。import语句以与平台不相关的方式,提供了目录路径写法。

选择点号语法,一部分是考虑到跨平台,但也是因为import语句中的路径会变成实际的嵌套对象路径。这种语法也意味着,如果忘了在import语句中省略.py,就会得到奇怪的错误信息。例如,import mod.py会被看成是目录路径导入:这是要载入mod.py,但解释器却试着载入mod\py.py,而最终就是发出可能令人困惑的错误信息。

__init__.py包文件

如果选择使用包导入,就必须多遵循一条约束:包导入语句的路径中的每个目录内都必须有__init__.py这个文件,否则导入包会失败。

也就是说,在所采用的例子中,dir1和dir2内都必须包含__init__.py这个文件。容器目录dir0不需要这类文件,因为其本身没列在import语句之中。更正式说法是,像这样的目录结构:

以及这种形式的import语句:

必须遵循下列规则:

  • dir1和dir2中必须都含有一个__init__.py文件
  • dir0是容器,不需要__init__.py文件;如果有的话,这个文件也会被忽略。
  • dir0(而非dir0\dir1)必须列在模块搜索路径上(也就是此目录必须是主目录,或者列在PYTHONPATH之中)。

结果就是,这个例子的目录结构应该是这样(以缩进表示目录嵌套结果)。

__init__.py可以包含Python程序代码,就像普通模块文件。这类文件从某种程度上讲就像是Python的一种声明,尽管如此也可以完全是空的。作为声明,这些文件可以防止有相同名称的目录不小心隐藏在模块搜索路径中,而之后才出现真正所需要的模块。没有这层保护,Python可能会挑选出和程序代码无关的目录,只是因为有一个同名的目录刚好出现在搜索路径上位置较前的目录内。

更通常的情况下,__init__.py文件扮演了包初始化的钩子、替目录产生模块命名空间以及使用目录导入时实现from*(也就是from...import*)行为的角色。

包的初始化

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,会让变量名X可在该目录的命名空间内使用(将在后面章节看到__all__的另一种用法)。

如果用不着这类文件,也可以让这类文件保持空白。不过,为了让目录导入完全运作,这类文件就得存在。

注意:不要把包__init__.py文件和后面介绍的类__init__构造函数方法搞混淆了。前者是当导入初次遍历一个包目录的时候所运行的代码的文件,而后者是当创建实例的时候才调用。它们都具有初始化的作用,但是它们有很大的不同。

包导入实例

编写刚才所谈的例子,来说明初始化文件和路径是如何运作的。

下列三个文件分别位于目录dir1和dir1的子目录dir2中——这些文件的路径名在注释中给出:

这里,dir1要么是工作所在目录(也就是主目录)的子目录,要么就是位于模块搜索路径中(技术上就是sys.path)的一个目录的子目录。无论哪一种,dir1的容器都不需要__init__.py文件。

当Python向下搜索路径时,import语句会在每个目录首次遍历时,执行该目录的初始化文件。print语句加在这里,用来跟踪它们的执行。

此外,就像模块文件一样,任何已导入的目录也可以传递给reload,来强制该项目重新执行。就像这里展示的那样,reload可以接受点号路径名称,来重载嵌套的目录和文件。

导入后,import语句内的路径会变成脚本的嵌套对象路径。在这里,mod是对象,嵌套在对象dir2中,而dir2又嵌套在对象dir1中。

实际上,路径中的每个目录名称都会变成赋值了模块对象的变量,而模块对象的命名空间则是由该目录内的__init__.py文件中所有赋值语句进行初始化的。

dir1.x引用了变量x,x是在dir1\__init__.py中赋值的,而mod.z引用的变量z则是在mod.py内赋值的。

包对应的from语句和import语句

import语句和包一起使用时,有些不方便,因为必须经常在程序中重新输入路径。

例如,上一节的例子中,每次要得到z时,就得从dir1开始重新输入完整路径,并且每次都要重新执行整个路径。如果想要尝试直接读取dir2或mod,就会得到一个错误。

因此,让包使用from语句,来避免每次读取时都得重新输入路径,通常这样比较方便。也许更重要的是,如果重新改变目录树结构,from语句只需在程序代码中更新一次路径,而import则需要修改很多地方。import作为一个扩展功能(下一章讨论),在这里也有一定的帮助,它提供一个完整路径较短的同义词:

为什么要使用包导入

如果刚学Python,确认已经精通了简单的模块,才能进入包的领域,因为这里有些高级的功能。

然而,包扮演了重要的角色,尤其是在较大程序中:包让导入更具信息性,并可以作为组织工具,简化模块的搜索路径,而且可以解决模糊性。

首先,因为包导入提供了程序文件的目录信息,因此可以轻松地找到文件,从而可以作为组织工具来使用。没有包导入时,通常得通过查看模块搜索路径才能找出文件。再者,如果根据功能把文件组织成子目录,包导入会让模块扮演的角色更为明显,也使代码更具可读性。例如,正常导入模块搜索路径上某个目录内的文件时,就像这样:

与下面包含路径的导入相比,提供的信息就更少:

包导入也可以大幅简化PYTHONPATH和.pth文件搜索路径设置。实际上,如果所有跨目录的导入,都使用包导入,并且让这些包导入都相对于一个共同的根目录,把所有Python程序代码都存在其中,在搜索路径上就只需一个单独的接入点:通用的根目录

最后,包导入让你想导入的文件更明确,从而解决了模糊性。

举例三个系统

实际中需要包导入的场合,就是解决当多个同名程序文件安装在同一个机器上时,所引发的模糊性。这是与安装相关的问题,也是通常实际中所要留意的地方。用一个假设的场景来说明。

假设程序员开发了一个Python程序,它包含了一个文件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文件内的一些程序代码。毕竟,这是通用工具的代码,而Python代码的本质都是想再利用的。就此而言,想在第三个目录内所编写的文件内写下了下面的代码,来载入两个文件其中的一个:

现在,问题开始出现了。为了让这能够工作,需要设置模块搜索路径,引入包含utilities.py文件的目录。但是,要在路径内先放哪个目录呢:system1还是system2?

这个问题在于搜索路径本质上是线性的。搜索总是从左至右扫描,所以不管这个困境你想多久,一定会得到搜索路径上最左侧(最先列出)的目录内的utilities.py。也就是说,永远无法导入另一个目录的那个文件。每次导入操作时,可以试着在脚本内修改sys.path,但那是外部的工作,很容易出错。在默认情况下,可以说你走到了一个死胡同。

这个问题正是包所能够解决的。不要在单独的目录内把文件安装成单纯的文件列表,而是将它们打包,在共同根目录之下,安装成子目录。例如,可能想组织这个例子中的所有代码,变成下面这样的安装层次。

现在,就是把共同根目录添加到搜索路径中。如果程序代码的导入就相对于这个通用的根目录,就能以包导入,导入任何一个系统的工具文件:该文件所在目录名称会使其路径具有唯一性(因此,引用的模块也是这样)。事实上,只要使用import语句,就可以在同一个模块内导入这两个工具文件,而每次引用工具模块时,都要重复其完整的路径。

在这里,所在目录名称让模块的引用变得具有唯一性。

注意:如果需要读取两个或两个以上路径内的同名属性时,才需要使用import,在这种情况下不能用from。如果被调用的函数名称在每个路径内都不同,from语句就可以避免每当调用其中一个函数时,就得重复完整的包的路径的问题,这一点先前已经说过。

此外,注意到,在前边所展示的安装层次中,__init__.py文件已加入到system1和system2目录中来使其工作,但是不需要在根目录内增加。只有在程序代码内,import语句所列的目录才需要这些文件。回想一下,Python首次通过包的目录处理导入时,这些文件就会自动运行了。

从技术上来讲,在这种情况下,system3目录不需要放在根目录下:只有被导入的代码包需要。然而,因为不知道何时模块可能在其他程序中有用,你可能还是想将其放在通用的根目录下,来避免以后类似的变量名冲突问题。

最后,注意两个初始系统的导入依然正常运作。因为它们的主目录都会先搜索,在搜索路径上多余的通用根目录,对于system1和system2内的代码是不相关的。它们只需写import utilities,就可以找到自己的文件。再者,如果在通用的根目录下解开所有的Python系统,路径配置就会变得很简单:只需要一次添加通用的根目录就可以了。

包相对导入

目前为止,对包导入的介绍主要集中在从包的外部导入包文件。在包自身的内部,包文件的导入可以使用和外部导入相同的路径语法,但是,它们也可能使用特殊的包内搜索规则来简化导入语句。也就是说,包内的导入可能相对于包,而不是列出包导入路径。

也就是显式地相对导入语法,通过使得相同的包的导入更为明显,从而增强代码的可读性。

对于包中的导入,Python 3.0引入了两个变化:

  • 模块导入搜索路径语义默认地跳过包自己的目录。导入只是检查搜索路径的其他组件。这叫做“绝对”导入。
  • 扩展了from语句的语法,以允许显式地要求导入只搜索包的目录。这叫做“相对”导入语法。

基础知识

from语句现在可以使用前面的点号(“.”)来指定,它们需要位于同一包中的模块(所谓的包相对导入)而不是位于模块导入搜索路径上某处的模块(叫做绝对导入)。也就是说:

可以使用from语句前面的点号来表示,导入应该相对于外围的包——这样的导入将只是在包的内部搜索,并且不会搜索位于导入搜索路径(sys.path)上某处的同名模块。直接效果是包模块覆盖了外部的模块。

在Python 3.0中,在一个包中导入默认是绝对的——在缺少任何特殊的点语法的时候,导入忽略了包含包自身并在sys.path搜索路径上的某处查找。

例如下面的一条语句:

告诉Python把位于与语句中给出的文件相同包路径中的名为spam的一个模块导入。类似的,语句:

意味着“从名为spam的模块导入变量name,而这个spam模块与包含这条语句的文件位于同一个包下。”

在Python 3.0中,不带点号的一个import总是会引发Python略过模块导入搜索路径的相对部分,并且在sys.path所包含的绝对目录中查找。例如,在Python 3.0的方式中,如下形式的一条语句,总是在sys.path上的某处查找一个string模块,而不是查找该包中具有相同名称的模块。

运行如下形式的一条语句,来强制一个相对导入。

Python 3.0中的方式的唯一区别是,当模块给定一个简单的名称,要把文件所在的同样的包目录中的该模块载入,它是必需的方式。

注意,前面的点号可以用来仅对from语句强制相对导入,而不对import语句这样。在Python 3.0中,import modname语句形式如今仍然执行相对导入(例如,首先搜索包的目录)。前面没有点号的from语句与import语句的行为相同,在Python 3.0中是绝对的(略过包目录)

其他的基于点的相对引用模式也是可能的。在位于名为mypkg的一个包目录中的一个模块文件中,如下替代的import形式也像所述的那样工作。

为什么使用相对导入

设计初衷是,当脚本在同名文件出现在模块搜索路径上许多地方时,可以解决模糊性。考虑如下包的目录。

这定义了一个名为mypkg的包,其中含有名为mypkg.main和mypkg.string的模块。现在,假设模块main试图导入名为string的模块。

不过,import的原意可能是要导入Python标准库的string模块。无法使用包导入路径来解决这个问题,因为无法依赖在每台机器上都存在标准链接库以上的额外包的目录结构。

换句话说,包中的导入可能是模糊的。在包内,import spam语句是指包内或包外的块,这并不明确。更准确地讲,一个局部的模块或包可能会隐藏sys.path上的另一个模块,无论是有意或无意的。

实际上,Python用户可以避免为他们自己的模块重复使用标准库模块的名称(如果需要标准string,就不要把新的模块命名为string)。但是,这样对包是否会不小心隐藏标准模块没有什么帮助。再者,Python以后可能新增标准库模块,而其名称刚好和自己的一个模块同名。依赖相对导入的程序代码比较不容易理解,因为对于期望使用哪个模块,可能会感到困惑。如果程序代码中能明确地进行分辨就比较好。

相对导入解决方案

要解决这种问题,在包中运行的导入已经在Python 3.0中改为绝对的。在这种方式下,示例文件mypkg/main.py中,一条如下形式的import语句将总是在包之外找到一个string,通过sys.path的绝对导入搜索。

没有前面的点号的一条from语句,也看做是绝对的。

如果真的想要从包中导入一个模块,而没有给出从包根目录的完整路径,在from语句中使用点语法仍然可能做到相对导入。

这种形式只是相对于当前的包导入string模块,并且是前面的导入示例的绝对形式的相对等价形式,当使用这一特殊的相对语法的时候,包的目录是唯一搜索的目录。

也可以使用相对语法从一个模块复制特定的名称。

这条语句再次相对于当前包来引用string模块。如果这段代码出现在mypkg.main模块中,它将从mypkg.string导入name1和name2。

实际上,相对导入中的“.”用来表示包含文件的包目录,而导入就出现在该文件中。前面再增加一个点,将执行从当前包的父目录的相对导入。例如,下面的语句。

将会导入mypkg位于该包自己的包含目录中的spam模块,该模块紧挨着mypkg。更通俗地说,位于某个模块A.B.C中的代码可以做下面任何一种导入。

相对导入VS绝对包路径

此外,一个文件有时候也可以在一条绝对导入语句中显式地指定其包。例如,在下面的语句中,将在sys.path的一个绝对路径中找到mypkg。

然而,这依赖于配置以及模块搜索路径设置的顺序,尽管相对导入的点语法不会依赖于此。实际上,这种形式需要直接包含将要包含在模块搜索路径中的mypkg。通常,像这样显式地指定包的时候,绝对导入语句必须列出包的根目录下的所有目录。

在较大或较深的包中,这可能比点语法要做更多的工作。

使用后一种形式,包含的包自动搜索,而不管搜索路径的设置是什么。

相对导入的作用域

相对导入乍看可能有些令人困惑,但是如果记住一些关键点,会很有帮助。

  • 相对导入适用于只在包内导入。记住,这种功能的模块搜索路径修改只应用于位于包内的模块文件中的import语句。位于包文件之外的通常的导入将像前面所介绍的那样工作,首先自动搜索包含了顶级脚本的目录。
  • 相对导入只是用于from语句。还要记住,这一功能的新的语法只是用于from语句,而不适用于import语句。可以这样检测它,一个from中的模块名前面有一个或多个点号。包含点号但前面没有一个点号的模块名是包导入,而不是相对导入。
  • ·术语含糊不清。坦率地讲,用来描述这一功能的术语可能太过令人混淆了。实际上,所有的导入对于某些事物来说都是相对的。在一个包外部,导入仍然是相对于sys.path模块搜索路径上列出的目录的。这个路径包含了程序的包含目录、PYTHONPATH设置、路径文件设置以及标准库。当交互地工作的时候,该程序的包含目录直接就是当前的工作目录。

对于包内部进行的导入,在Python 3.0的方式中,真正变化的只是常规的“绝对”导入语法忽略了一个包目录,但是,特殊的“相对”导入语法使其首先搜索并仅搜索它。

提及Python 3.0的导入是“绝对的”,真正的含义是它是相对于sys.path上的目录的,而不是相对于包本身。

当提及“相对”导入,意思是,它们只是相对于包目录的。当然,一些sys.path目录是绝对路径或相对路径(可能把事情讲的更容易令人混淆了些,但这可能是一种思路拓展)。

模块查找规则总结

使用包导入和相对导入,Python 3.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查找。例如,在from.import A中,模块搜索限制在包含了该语句中出现的文件的目录之中。

相对导入应用

来看一些快速简单案例

在包之外导入

首先,正如前面所提到的,这一功能不会影响到一个包之外的导入。因此,如下的代码像预期的那样查找标准库string模块。

但是,如果在所工作的目录中添加一个同名的模块,将会选择该模块,因为模块搜索路径的第一条是当前工作目录(CWD)

换句话说,常规的导入仍然相对于“主”目录(顶级的脚本的包含目录,或者我们正在工作的目录)。实际上,如果不是在用作一个包的部分的一个文件中,甚至不允许相对导入语法。

在交互提示模式中输入的代码与将其放在一个顶层脚本中运行的行为是相同的,因为sys.path上的第一个条目要么是交互的工作目录,要么是包含顶层文件的目录。唯一的区别是,sys.path的开始是一个绝对路径,而不是一个空字符串。

包内的导入

来去除在CWD中编写的本地string模块,并构建带有两个模块的一个包目录,包括必需空的test\pkg\__init__.py文件(在这里省略了)。

这个包中的第一个文件试图用一条常规的import语句导入第二个文件。在3.0中是失败的,因为是绝对的。

使用特殊的相对导入语法来修改第一个文件。这样就不会报错了。

导入仍然是相对于CWD的

注意,在前面的示例中,包模块仍然必须访问string这样的标准库模块。实际上,它们的导入仍然相对于模块搜索路径上的条目,即便这些条目自身是相对的。

如果再次向CWD添加了一个string模块,包中的导入将会在那里找到它,而不是在标准模块库中。尽管可以在Python中略过带有一个绝对导入的包目录,仍然不能略过导入包的程序的主目录。

使用相对导入和绝对导入选择模块

为了展示这如何应用于标准模块库的导入,再一次重新设置包。去除掉本地string模块,并在包自身之中定义一个新的。

现在,获得哪个版本的string模块取决于你使用哪个Python版本。和前面一样,Python 3.0把第一个文件中的导入解释为绝对的并略过了该包

在Python 3.0中使用相对导入语法会迫使再次搜索包,就好像在Python 2.6中一样——通过在Python 3.0中使用绝对和相对导入语法,可以显式地跳过或选择包目录。实际上,这是Python 3.0方式所解决的情况。

相对导入语法实际上是一种绑定声明,而不只是一种偏好,注意到这点很重要。

如果删除了这个例子中的string.py文件,string.py中的相对导入在Python 3.0和Python 2.6中都会失败,而不是回归到这一模块(或任何其他模块)的标准库版本。

相对导入所引用的模块必须在包目录中存在。

导入仍然是相对于CWD的

尽管绝对导入允许略过包模块,它们仍然依赖于sys.path上的其他部分。

定义自己的两个string模块。在下面的代码中,CWD中有一个为该名称的模块,包中有一个该名称的模块,并且,另一个string模块位于标准库中。

使用相对导入语法导入string模块时,我们如愿地得到了包中的那个版本。

Python 3.0将其看做“绝对的”,在这种情况下,真的意味着它略过包并载入相对于CWD的版本(而不是相对于标准库的版本)。

尽管包显式地要求目录中的模块,它们的导入仍然是相对于常规模块搜索路径的剩余部分。在这个例子中,程序中的一个文件使用的包隐藏了该包可能想要的标准库模块。在Python 3.0中真正发生的变化只是允许包代码在包的内部或外部选择文件。由于导入方法可能依赖于无法预见的一个封闭环境,Python 3.0中的绝对导入并不能保证找到标准库中的模块。

实际上,并不像宣传的那样。在开发中,通常可以构建自己的导入、搜索路径和模块名称,使其按预想的工作。

然而应该记住,在一个较大系统中的导入可能取决于所使用的环境,并且模块导入协议是一个成功的库设计的一部分。

注意:既然已经学习了有关包相对导入的知识,还要记住,它们不总是最佳选择。绝对包导入,相对于sys.path上的一个路径,显式包相对导入语法中,有时候绝对导入都是首选。

包相对导入语法和Python 3.0的新的绝对导入搜索规则,至少从一个包的相对导入要显式化,并且由此更容易理解和维护。使用带点号的导入的文件,显式地绑定到一个包目录,并且需要做代码修改才能随处使用。

当然,这对于你的模块的影响程度可能随每个包而不同,当识别目录时,绝对导入也可能需要修改。

为什么要在意:模块包

现在,包是Python的标准组成部分,常常见到较大的第三方扩展都是以包目录的形式分发给用户,而不是单纯的模块列表。例如,win32all这个Python的Windows扩展包,是首先采用包这种潮流的扩展之一。其许多工具模块都位于包内,并且通过路径来导入。例如,需要加载客户端COM工具,就需要使用如下的语句:

这一行代码会从win32com包(一个安装子目录)的client模块取出一些变量名。

包导入在Jython(Python的Java实现版本)所运行的代码中也很普遍,因为Java库也是组织为层次结构的。email和XML工具,也像这样组织了标准库内的子目录,并且Python 3.0组甚至与包中的模块更为相关(包括tkinterGUI工具、HTTP网络连接工具等)。如下的语句导入了对Python 3.0中的各种标准库工具的访问:

无论你是否将要建立包的目录,最终可能还是从包的目录导入的。

技巧

每次引用包的内容时,如何避免重复包的完整路径?

通过from语句使用包,直接把包的变量名复制出来,或者使用import语句的as扩展功能,把路径改为较短的别名。在这种情况下,路径只出现在了一个地方,就在from或import语句中。

在什么情况下必须通过import而不能通过from使用包?

只有在你需要读取定义在一个以上路径的相同变量名时,才必须通过import来使用包,而不能使用from。使用import,路径可让引用独特化,然而,from却让任何变量名只有一个版本。

  • 15
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

兴焉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值