Python基础手册29——包

六、包

在 Python 中,导入除了可以指定模块名之外,也可以指定目录路径。为了帮助组织模块并提供命名层次结构,Python 有一个概念:包。

你可以将程序包视为文件系统上的目录,将模块视为目录中的文件,但是包和模块并不需要源自文件系统。像文件系统目录一样,包是以分层方式组织的,包本身可以包含子包以及常规模块。事实上,包导入是把计算机上的目录编成另一个 Python 的命名空间,而包对象的属性则对应于目录中所包含的子目录和模块文件。

Python 中只有一种模块类型,不管这个模块是否是用 Python、C,或者其他语言实现。包是一种特殊的模块,或者换句话说,所有的包都是模块,但不是所有的模块都是包。具体来说,包含 __path__ 属性的任何模块都被视为包。

所有模块都有一个名称,子包名称(或者文件模块名)与其父包名称之间用点号 . 分隔,类似于 Python 的标准属性访问语法。使用标准的 import 和 from-import 语句导入包中的模块。您可能有一个名为 sys 的模块和一个名为 email 的软件包,其中包含一个名为 email.mime 的子包,还有名为 email.mime.text 的子包。

包提供的层次结构对于组织大型系统内的文件会很方便,而且可以简化模块搜索路径的设置。当多个同名的程序文件安装在某一机器上时,包导入也可以偶尔来解决导入的不确定性。

包是一个有层次的文件目录结构,它定义了一个由 模块 和 子包 组成的 Python 应用程序执行环境。Python 1.5 加入了包,用来帮助解决如下问题:

  • 为平坦的名称空间加入有层次的组织结构
  • 允许程序员把有联系的模块组合到一起
  • 允许分发者使用目录结构而不是一大堆混乱的文件
  • 帮助解决有冲突的模块名称


Regular packages

Python 定义了两种类型的包,regular packages 和 namespace packages。

常规包是传统包,常规包通常实现为包含 __init__.py 文件的目录。当导入常规包时,这个 __init__.py 文件被隐式执行,它定义的对象被绑定到包名称空间中的名称。__init__.py 文件可以包含与任何其他模块可以包含的相同的 Python 代码,Python 会在导入时为模块添加一些其他属性。

例如,以下文件系统布局定义具有三个子包的顶层 parent 包:

导入 parent.one 将隐式执行 parent/__init__.py 和 parent/one/__init__.py 。随后导入 parent.two 或 parent.three 将执行 parent/two/__init__.py 和 parent/three/__init__.py。

下面的章节主要介绍常规包的概念。

Namespace packages

命名空间包是各种 portions 的组合,其中每个部分都向父包提供子包。其可以存在于文件系统上的不同位置,也可以在 zip 文件,网络或 Python 在导入期间搜索的任何其他位置找到。命名空间包可以或可以不直接对应于文件系统上的对象;它们可以是没有具体表示的虚拟模块。

命名空间包不为其 __path__ 属性使用普通列表。它们改为使用自定义可迭代类型,如果其父包的路径发生变化(或对于顶级包的 sys.path),它将在该包中的下一次导入尝试时自动执行对包部分的新搜索变化。

使用命名空间包,没有 parent/__init__.py 文件。实际上,在导入搜索期间可能会有多个 parent,其中每个目录由不同的部分提供。因此,parent/one 可能不在物理上位于parent/two 旁边。在这种情况下,Python 将为顶层 parent 包创建一个命名空间包,只要它或其中一个子包被导入。

1、包导入基础

在 import 语句中列举简单文件名的地方,可以改成列出路径的名称,彼此以点号相隔:
import dir1.dir2.mod

form 语句也是一样的:
from dir1.dir2.mod import x

这些语句的点号 . 路径是对应于物理机上的目录层次的路径,通过这个路径可以获得到文件 mod.py(或类似文件,扩展名可能会有变化)。也就是说,上面的语句是表明了机器上有个目录 dir1,而 dir1 里面有子目录 dir2,而 dir2 内包含有一个名为 mod.py (或其他格式文件)的模块文件。
这些导入意味着,dri1 目录位于某个目录 dir0 中,这个 dir0 目录可以在 Python 模块搜索路径中找到。容器目录 dir0 需要添加在模块搜索路径中(除非这是顶层文件的主目录)。

dir0/dir1/dir2/mod.py

import 语句中的目录路径只能是以点号间隔的变量。选择点号语法,一是考虑到跨平台,同时也是因为 import 语句中的路径会变成实际的嵌套对象的路径。这种语法也意味着,如果你忘记了在 import 语句中省略 .py,就会得到奇怪的错误信息。

__init__.py 包文件

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

也就是在 dir0/dir1/dir2/mod.py 的路径下必须保证在 dir1 和 dir2 中必须都含所有一个__init__.py 文件;dir0 是容器,不需要 __init__.py 文件,如果有的话,这个文件也会被忽略,dir0(而非dir0/dir1)必须列在模块搜索路径上。

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

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

1、包的初始化

Python 首次导入某个目录时,会自动执行该目录下 __init__.py 文件中的所有程序代码。因此,这类文件自然就是放置包内文件所需要初始化的代码的场所。例如,包可以使用其初始化文件,来创建所需要的数据文件、连接数据库等。

2、模块命名空间的初始化

在包导入的模型中,脚本内的目录路径,在导入后会成真实的嵌套对象路径。导入的包对应的目录内的 __init_.py 文件会在导入时执行,其生成的所有变量名都会成为生成的模块对象的属性。__init__.py 文件为目录所创建的包模块对象提供了命名空间。

3、from * 语句的行为

作为一个高级功能,你可以在 __init__.py 文件内使用 __all__ 列表来定义目录以 from * 语句形式导入时,需要导出什么。在 __init__.py 文件中,__all__ 列表是指当包(目录)名称使用 from * 的时候,应该导入的子模块的名称清单。如果没有设定 __all__ ,from * 语句不会自动加载嵌套于该目录内的子模块。取而代之的是,只加载该目录的 __init__.py 文件中赋值语句定义的变量名,包括改文件中程序代码明确导入的任何子模块。


2、包导入实例

当 Python 向下搜索路径时,import 语句会在每个目录首次遍历时,执行该目录的初始化文件。假定有如下的目录结构:

phone 是最顶层的包,voicedta 等是它的子包。 我们可以这样导入子包:

你也可使用 from-import 语句直接导入最底层的模块或模块对象的属性:

执行了上面的导入操作后,路径中的每个目录名称都会变成赋值了包模块对象的属性,而包模块对象的命名空间则是由该目录内的 init.py 文件中所有赋值语句进行初始化的。

包对应的 form 语句和 import 语句

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

因此,让包使用 from 语句来避免每次读取时都得重新输入路径。更重要的是,如果你重新改变目录树结构,from 语句只需在程序代码中更新一次路径,而 import 则需要修改很多地方。


为什么使用包导入

包让导入更具信息性,并可以作为组织工具,简化模块的搜索路径,而且可以解决模糊性。

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

和下面包含路径的导入相比,提供的信息回显的更少。

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


3、包的相对导入

在包自身的内部的文件中导入包中的模块可以使用和外部导入相同的路径语法,但是,它们也可能使用特殊的包内搜索规则来简化导入语句。也就是说,包内的导入可能相对于包,而不是列出完整的包导入路径。而且,当文件中导入的模块的对应文件出现在模块搜索路径上许多地方时,可以解决模糊性。

相对导入基础知识

我们可以在 from 语句 后通过使用点号(.)来指定当前目录,这可以导入位于同一包中的模块(所谓的包相对导入),而不是位于模块导入搜索路径上某处的模块(叫做绝对导入)。相对导入的点号用来表示当前文件的目录。前面再增加一个点号,将执行从当前文件目录的父目录相对导入。

在 Python3.0 中,不带点号的一个 import 总是会引发 Python 在 sys.path 所包含的目录中查找。下面的例子,第一个 import 语句成功导入,第二个 import 导入失败。

点号只可以用来对 from 语句执行强制相对导入,而不能对 import 语句这样。前面没有点号的 from 语句与 import 语句的行为相同。


4、模块查找规则总结

简单模块名通过搜索 sys.path 路径列表上的每个目录来查找,从左到右进行。这个列表由系统默认设置和用户配置设置组成。

包是带有一个特殊的 __init__.py 文件的目录,这使得一个导入中可以使用 A.B.C 目录路径语法。

在一个包文件中,常规的 import、from 语句使用和其他地方的导入一样的 sys.path 搜索规则。包中的相对导入使用 from 语句以及后面的点号,它是相对于包的;也就是说,只检查包目录,并且不使用常规的 sys.path 查找。


《Python基础手册》系列:

Python基础手册 1 —— Python语言介绍
Python基础手册 2 —— Python 环境搭建(Linux)
Python基础手册 3 —— Python解释器
Python基础手册 4 —— 文本结构
Python基础手册 5 —— 标识符和关键字
Python基础手册 6 —— 操作符
Python基础手册 7 —— 内建函数
Python基础手册 8 —— Python对象
Python基础手册 9 —— 数字类型
Python基础手册10 —— 序列(字符串)
Python基础手册11 —— 序列(元组&列表)
Python基础手册12 —— 序列(类型操作)
Python基础手册13 —— 映射(字典)
Python基础手册14 —— 集合
Python基础手册15 —— 解析
Python基础手册16 —— 文件
Python基础手册17 —— 简单语句
Python基础手册18 —— 复合语句(流程控制语句)
Python基础手册19 —— 迭代器
Python基础手册20 —— 生成器
Python基础手册21 —— 函数的定义
Python基础手册22 —— 函数的参数
Python基础手册23 —— 函数的调用
Python基础手册24 —— 函数中变量的作用域
Python基础手册25 —— 装饰器
Python基础手册26 —— 错误 & 异常
Python基础手册27 —— 模块
Python基础手册28 —— 模块的高级概念
Python基础手册29 —— 包

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容

  • Python 面向对象Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对...
    顺毛阅读 4,211评论 4 16
  • 一、模块 1、模块和导入 当程序代码量变得相当大、逻辑结构变得非常复杂的时候,我们最好把代码按照逻辑和功能划分成一...
    常大鹏阅读 2,976评论 0 9
  • 定义类并创建实例 在Python中,类通过 class 关键字定义。以 Person 为例,定义一个Person类...
    绩重KF阅读 3,931评论 0 13
  • 自从生娃闭关一年后,差点以为自己已经失去了沟通社交的能力。你知道的,技能这种东西,除去天分,全靠熟能生巧。反正我们...
    禾禾火火阅读 256评论 3 4
  • 这些内容应该写进日记的,但是我把日记本丢在办公室了。这是一个随意的题目,和它一样随意。它无数次出现在我的高...
    默生花阅读 1,138评论 0 2