骑麦兜看落日

[Code]Python学习手册_第五部分_模块

字数统计: 17k阅读时长: 68 min
2018/08/10 Share

第二十一章 模块:宏伟蓝图

  • 深入学习Python模块
  • 介绍模块、属性以及导入的基础知识
  • 探索import语句的操作
  • 学到导入模块搜索路径与配置搜索路径
  • 介绍命名空间的概念

为什么使用模块

  • 代码重用

    模块可以在文件中永久保存代码,可以按照需要任意次数地重新载入和重新运行模块

    模块是封装变量名的命名空间,模块文件顶层定义的所有变量名被认作是导入模块的对象属性,可以被多个外部的客户端引用

    模块允许将独立的文件连接成一个更大的程序系统

  • 系统命名空间的划分

    模块是Python中最高级别的程序组织单元

    模块通过封装自包含的变量的软件包,即命名空间提供了将部件组织为系统的简单方法,这一点对避免变量名的冲突很有帮助,如果不精确导人文件的话,就无法看到另一个文件中的变量名

    模块文件的全局作用域成为模块对象的命名空间

  • 实现共享服务和数据

    从操作的角度来看,模块对实现跨系统共享的组件是很方便的,而且只需要一个拷贝即可

    一个模块中的全局对象可以背一个以上的函数或文件使用


Python程序架构

Python程序的架构是将一个程序分割为源代码文件的集合以及将这些部分连接在一起的方法

如何组织一个程序

程序是作为一个主体的、顶层的文件配合有零个或多个支持的文件来构造的,这些文件称作模块

在Python中,顶层文件包含了程序的主要的控制流程,模块文件就是工具的库,这些工具提供顶层文件或其他地方使用的组件

模块文件通常在运行时不需要直接做任何事,可以导入模块来获取或使用模块定义的工具,即模块的属性

导入和属性

模块是最高级别的组织结构,也是Python中程序代码重用的最高层次,模块中编写的组件可以让其他程序使用

任何文件都能从任何其他文件中导入其工具,导入链要多深就有多深

import语句可以在运行时载入其他文件,给顶层文件提供了模块文件全局作用域所有对象的访问权限

import执行时解析模块文件,创建模块对象及对象中的语句并将模块对象赋值给模块名

适用对象属性语法object.attribute可以调用对象的属性的值,有些属性是可调用的对象,有些对象是数据数值

标准库模块

Python自带的模块集合称为标准链接库,大约有200个模块,包含与平台不相关的常见程序设计任务:操作系统接口、对象永久保存、文字模式匹配、网络和Internet脚本、GUI建构等,这些工具不是Python语言的组成部分,但是可以在任何安装了标准Python的情况下导入适当的模块使用

可以通过Python标准库参考手册查看整体的标准库模块


import如何工作

Python把载入的模块存储到名为sys.modules的表中,并在导入操作的开始检查该表,如果模块不存在,将会启动一个三个步骤的过程

导入是运行时的运算,第一次导入指定文件时执行三个步骤

  1. 找到模块文件
  2. 编译成位码(需要时)
  3. 执行模块的代码来创建其所定义的对象

若不是第一次导入会跳过这三个步骤,提取内存中已加载的模块对象

通过调用reload可以强制重新导入

搜索

首先,Python必须查找到import语句所引用的模块文件

Python使用标准模块搜索路径来找出import语句所对应的模块文件,所以模块名应省略路径和后缀

编译(可选)

必要时,Python会将符合import语句的源代码文件编译为字节码

Python会检查文件的时间戳,当已存在的字节码文件比源代码文件旧时会在程序运行时自动重新生成字节代码,否则会跳过源代码到字节码的编译步骤

Python在搜索路径上只发现字节码文件时会直接加载字节码文件

只有被导入的文件才会编译生成.pyc文件,顶层文件通常直接执行,其字节码在内部使用后被丢弃所以没有.pyc文件

运行

import操作的最后步骤是执行模块的字节码,模块文件中顶层代码做的实际工作会在导入时看到其结果

文件中所有语句依次执行,对变量名的赋值运算都会产生模块文件的属性,生成模块代码定义的所有工具


模块搜索路径

导入过程最重要及最早的部分是定位要导入的文件,定位依赖模块导入搜索路径的自动特性或手动配置

Python的模块搜索路径是主要组件组合的结果,其中第一和第三进行了预先定义,第二和第四可以进行拓展路径,Python将收集它所找到的所有路径文件中的目录名,并且过滤掉任何重复的和不存在的目录

  1. 程序的主目录

    Python首先会在主目录内搜索导入的文件

    这个入口是包含所运行的程序的顶层脚本文件的目录,交互模式下,这一入口是当前工作的目录

    因为这个目录总是先被搜索,如果程序完全位于单一目录,所有导入自动工作,而并不需要配置路径

    由于这个目录是先搜索的,其文件将覆盖路径上的其他目录中具有同样名称的模块,可能会意外隐藏库模块

  2. PYTHONPATH目录

    之后,Python会从左至右搜索PYTHONPATH环境变量设置中罗列出的所有目录

    PYTHONPATH是设置包含Python程序文件的目录的列表,可以添加想导入的目录来扩展模块搜索的路径

    Python会先搜索主目录,当导人的文件跨目录时,这个设置才显得格外重要

  3. 标准链接库目录

    接着,Python会自动搜索标准库模块的安装目录

    因为这些一定会被搜索,通常是不需要添加到PYTHONPATH之中或包含到路径文件中的

  4. 任何.pth文件的内容

    最后,Python允许用户把有效的目录添加到.pth后缀的文本文件中

    .pth文件放在Python安装目录的顶层或者在标准库所在位置的sitepackages子目录中扩展模块搜索路径

    某些用户文本文件可能比环境设置更容易编码

    路径文件作为第三方库经常使用,它通常在Python的site-packages目录安装一个路径文件,从而不需要用户设置

配置搜索路径

搜索路径的PYTHONPATH和路径文件允许我们调整模块导入搜索路径

设置环境变量方法与存储路径文件位置随平台的变化而变化

搜索路径的变动

搜索路径的配置随平台及Python版本而异,附加的目录可能自动加入模块搜索路径

可以通过sys.path查看平台上配置的模块搜索路径

sys.path

通过打印内置的sys.path列表查看模块搜索路径的实际配置,打印的目录名称字符串列表就是Python内部实际搜索路径,Python由左至右搜索这个列表中的每个目录

Python在程序启动时将顶级文件的主目录、任何PYTHONPATH目录、已经创建的任何.pth文件路径及标准库目录合并,在导入时直接查找目录名的字符串列表

目录名的字符串列表提供了确认搜索路径是否成功配置的方法

目录名的字符串列表提供了通过修改sys.path使脚本暂时调整搜索路径的方式

模块文件选择

import语句省略文件名的后缀,Python选择在搜索路径中第一个符合导入文件名的文件

符合导入的后缀名

  • 源代码文件.py
  • 字节码文件.pyc
  • 目录、包的导入
  • 编译拓展模块(C或C++编写),动态链接(.so.dll.pyd)
  • C编写的编译好的内置模块通过静态链接至Python
  • ZIP文件组件,导入时自动解压缩
  • frozen可执行文件的内存内映像
  • Jython版本的Java类
  • IronPython版本的.NET组件

对于不同后缀名的同名文件,在不同目录中时按照由左至右的顺序加载sys.path中目录先出现的文件,在相同目录中时遵循标准挑选顺序,挑选顺序可能有所变化

高级的模块选择概念

导入钩子(import hook)可以重新定义import操作,__import__函数可用于定制import语句

通过在创建和执行时加入-O输出.pyo文件,比普通的.pyc文件快

第三方工具:distutils

Python的第三方拓展通常使用标准链接库中的distutils工具自动安装,不需要设置路径

使用distutils的系统一般都附带setup.py脚本进行程序的安装,这个脚本会导入并使用distutils模块将系统放在属于模块自动搜索路径一部分的目录内(通常在Lib/site-packages)


本章习题

  1. 模块源代码文件是怎样变成模块对象的

    模块的源代码文件在模块导人时,就会自动生成模块对象

    从技术角度来讲,模块的源代码会在导入时运行,一次一条语句,而在这个过程中赋值的所有变量名都会生成模块对象的属性

  2. 为什么需要设置PYTHONPATH环境变量

    设置PYTHONPATH可以从正在用的目录(交互模式下的当前目录,或者包含顶层文件的目录)以外的其他目录进行导人

  3. 举出模块导人搜索路径的四个主要组件

    模块导入搜索路径的四个主要组件是顶层脚本的主目录、列在PYTHONPATH环境变量中的所有目录、标准链接库目录以及位于标准位置中.pth路径文件中的所有目录

    程序员可以定制PYTHONPATH.pth文件

  4. 举出Python可能载入的能够响应import操作的四种文件类型

    Python可能载入源代码文件(.py) 、字节码文件(.pyc) 、C扩展模块(.so,.dll.pyd)以及相同变量名的目录(包导入)

    导入也可以加载更罕见的东西,例如,.ZIP文件组件、Python Jython版的Java类、IronPython的.NET组件以及没有文件形式的静态链接C扩展

    有了导入钩子,import可以加载任何东西

  5. 什么是命名空间?模块的命名空间包含了什么

    命名空间是一种独立完备的变量包,而变量就是命名空间对象的属性

    模块的命名空间包含了代码在模块文件顶层赋值的所有变量名(也就是没有嵌套于defclass语句中)

    从技术角度上来讲,模块的全局作用域会变成模块对象的属性命名空间

    模块的命名空间也会将其导入的其他文件中所做的赋值运算而发生变化


第二十二章 模块代码编写基础

  • 查看简单的模块实例
  • 讨论模块编码的基础知识:importfrom语句,reload调用
  • 研究命名空间的概念,学习导入嵌套命名空间细节,探索文件转换为模块的命名空间的过程
  • 学习from语句潜在的陷阱

模块的创建

使用文本编辑器输入Python代码保存为.py文件后被自动认为是Python模块,模块对应于Python程序文件

在模块顶层的所有变量名都会变成模块对象的属性,可以导出供客户端使用

只有需要导入时模块文件名才需要.py后缀,执行但不会被导入的顶层文件可有可无

导入后的模块名包导入中模块的文件名和目录名变成变量名,需要遵循普通变量名的命名规则

模块被导入时会将内部模块名映射到外部文件名,模块搜索路径中的目录路径加在前边,后缀名添加在后边

拓展模块是使用C或C++等外部语言编写代码创建的Python模块,作为Python脚本外部拓展库使用.被导入时拓展模块的外观和用法与Python源代码文件所编写的模块一样:通过import语句读取,提供函数和对象作为模块属性


模块的使用

在模块没有被加载时,通过importfrom语句搜索、编译以及执行模块文件程序

import会读取整个模块,需要定义模块名后才能读取它的变量名

from直接复制模块特定的变量名

import语句

1
2
3
4
5
6
7
8
9
10
# module1.py

def printer(v):
print(v)

% python

>>> import module1 # Get module as a whole
>>> module1.printer('Hello world!') # QUalify to get names
Hello world!

import语句使一个变量名引用整个模块对象,可以通过模块名得到该模块的属性

模块名可以被用来识别被载入的外部文件

模块名在文件加载后作为引用模块对象的变量名

from语句

1
2
3
4
5
6
7
8
9
10
# module1.py

def printer(v):
print(v)

% python

>>> from module1 import printer # Copy out one variable
>>> printer('Hello world!') # No need to qualify name
Hello world!

from语句把模块中的变量名复制到导入文件的作用域,可以在导入文件中不通过模块直接使用模块的变量名

from *语句

1
2
3
4
5
6
7
8
9
10
# module1.py

def printer(v):
print(v)

% python

>>> from module1 import * # Copy out all variables
>>> printer('Hello world!')
Hello world!

*会取得模块顶层所有赋了值的变量名的拷贝

from *把模块中所有变量名复制到导入的作用域,将一个模块的命名空间融入了另一个模块

导入只发生一次

模块只在第一次使用importfrom时载入并执行,之后的导入操作只会取出已加载是模块对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# simple.py

print('hello')
spam = 1 # Initialize variable

% python

>>> import simple # First import: loads and runs file's code
hello
>>> simple.spam # Assignment makes an attribute
1
>>> simple.spam = 2 # Change attribute in module
>>> import simple # Just fetches already loaded module
>>> simple.spam # Code wasn't rerun:attribute unchanged

由于模块文件中的顶层程序代码只执行一次,可以使变量进行初始化

print函数和=语句会在第一次导入时执行,变量在导入时初始化

第二次及以后的导入不会重新执行模块的代码,只是从Python内部模块表中取出已创建的模块对象,变量不进行初始化

import和from是赋值语句

importfrom是可执行的语句,Python在执行到这些语句时才会进行解析,被导入的模块和变量名只有这些语句执行后才可以使用

importfrom是隐性的赋值语句

  • import将整个模块对象赋值给一个变量名
  • from将一个或多个变量名赋值给另一个模块中同名的对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# small.py

x = 1
y = [1, 2]

% python

>>> from smalll import x, y # Copy two names out
>>> x = 42 # Changes local x only
>>> y[0] = 42 # Changes shared mutable in-place

>>> import small # Get module name(from doesn't)
>>> small.x # Small's x is not my x
1
>>> small.y # But we share a changed mutable
[42, 2]

赋值语句的内容适用于模块的读取,from复制的变量名导入模块内的变量名都是对共享对象的引用,对变量名重新赋值不会影响模块内的对象,对引用可变对象的变量名修改则会影响模块内的对象

文件间变量名的改变

1
2
3
4
5
6
7
8
9
10
11
12
# small.py

x = 1
y = [1, 2]

% 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

from赋值的变量名与导入模块的变量名没有联系,对其赋值只会修改当前作用域的变量

import可以修改另一个文件中的全局变量名

import和from的对等性

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

1
2
3
4
5
6
from module import name1, name2     # Copy these two names out(only)

import module # Fetch the module object
name1 = module.name1 # Copy names out by assignment
name2 = module.name2
del module # Get rid of the module name

from在导入者中创建新变量,并引用了导入模块中的同名对象,from *以同样的方式导入模块中所有的顶层变量名

from同样会把整个模块导入到内存中而不会只加载模块文件的一部分

from语句潜在的陷阱

from语句会让变量位置更隐秘和模块,但省略输入模块的变量名很方便

from导入变量时会覆盖掉作用域中现有的同名变量从而破坏命名空间,from *会把一个命名空间融入导入者命名空间无法看出变量名所属的模块,而import语句不存在这些问题

reload调用同时使用时,from导入的变量名可能扔引用之前版本的对象

简单模块倾向于使用import,from语句用于明确列举出想要的变量,from *语句应限制只用一次来确定变量名所属的模块

何时使用import

两个不同模块内定义相同变量名时必须使用import

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# M.py

def func():
...do something

# N.py

def func():
...do something
# O.py

from M import func # This overwrites one we got from M
from N import func # Calls N.func only
func()

无法使用from语句导入不同模块的同名变量

1
2
3
4
5
# O.py

import M, N # Get the whole modules, not their names
M.func() # We can call both names now
N.func() # The module names make them unique

使用import导入模块变量名可以访问不同模块的同名变量


模块命名空间

模块是命名空间(变量名建立所在的场所),存在于模块之内的变量名是模块对象的属性,模块是变量名的封装

模块对应文件,Python建立模块对象包含模块文件内所有变量

文件生成命名空间

模块文件顶层每一个赋值了的变量名都会变成模块的属性,形成命名空间

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

    系统中,模块在第一次导入时,Python会建立空的模块对象并逐一执行该模块文件内的语句

  • 顶层的赋值语句会创建模块属性

    在导人时,文件顶层赋值变量的语句会建立模块对象的属性,赋值的变量名会存储在模块属性的命名空间内

  • 模块的命名空间能通过属性__ dict__或dir(M)获取

    由导入建立的模块的命名空间是字典,可通过模块对象相关联的内置的__ dict__ 属性来读取, 而且能通过dir函数查看

    dir函数大至与对象的__ dict__属性的键排序后的列表相等,但是它还包含了类继承的变量名

  • 模块是一个独立的作用域(本地变量就是全局变量)

    模块顶层变量名遵循和函数内变量名相同的引用/赋值规则,但是顶层变量名本地作用域和全局作用域相同

    函数的变量名只会在函数执行是才存在,而模块导入后,模块文件的作用域就变成了模块对象属性的命名空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# module2.py

print('starting to load...')
import sys
name = 42
def func(): pass
class klass: pass
print('done loading.')

% python

>>> import module2
starting to load...
done loading.

>>> module2.sys
<module 'sys' (built-in)>
>>> module2.name
42
>>> module2.func
<function func at 0x103ff7400>
>>> module2.klass
<class 'module.klass'>

>>> list(module2.__dict__.keys())
['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__file__', '__cached__', '__builtins__', 'sys', 'name', 'func', 'klass']

模块首次导入时,Python从头到尾执行其中的语句,其中赋值语句会在模块命名空间内创建变量名

模块加载后,它的作用域就变成模块对象属性的命名空间

内部模块命名空间作为字典对象进行储存,有通用的字典方法,可以通过模块的__dict__属性获取模块命名空间字典

Python会在模块命名空间内加一些变量名,__file__指明模块从哪个文件加载,__name__指明导入者的名称

属性名的点号运算

通过.运算语法object.attribute可以获取任意object的attribute属性

.运算是表达式,传回和对象匹配的属性的值,不适用于作用域法则

  • 简单变量

    X是指在当前作用域内搜索变量名X,遵循LEGB规则运算

  • 点号运算

    X.Y是指在当前范围内搜索X,然后搜索对象X之中的属性Y,而非在作用域内

  • 多层点号运算

    X.Y. Z指的是寻找对象X之中的变量名Y,然后再找对象X.Y之中的Z

  • 通用性

    点号运算可用于任何具有属性的对象:模块、类、C扩展类型等

导入和作用域

不导入一个文件就无法存取该文件内所定义的变量名

变量由源代码中的赋值语句的位置决定,而属性伴随着对对象的请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# moda.py

X = 88 # My X: global to this file only
def f():
global X # Change this file's X
X = 99 # Cannot see names in other modules

# modb.py

X = 11 # My X: global to this file only

import moda # Gain access to names in moda
moda.f() # Sets moda.X, not this file's X
print(X, moda.X)

% python modb.py
11 99

导入操作不会赋予被导入文件中的代码上层代码的可见度:被导入文件无法看见进行导入的文件内的变量名

  • 函数绝对无法看见其他函数内的变量名,除非它们从物理上处于这个函数内
  • 模块程序代码绝对无法看见其他模块内的变量名,除非明确地进行了导人
  • 一段程序的作用域完全由程序所处的文件中实际位置决定,作用域不会被函数调用或模块导入影响

命名空间的嵌套

导入不会使命名空间向上嵌套,但会发生向下的嵌套,利用属性的.运算可能深入到任意嵌套的模块中并读取其属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
  # mod3.py

x = 3

# mod2.py

X = 2
import mod3

print(X, end=' ') # My global X
print(mod3.X) # mod3's X

# mod3.py

X = 1
import mod2

print(X, end=' ') # My global X
print(mod2.X, end=' ') # mod2's X
print(mod2.mod3.X) # Nested mod3's X

% python mod1.py
2 3
1 2 3
mod1`导入`mod2`时创建一个两层的嵌套命名空间,利用`mod2.mod3.X`变量名路径可以深入到导入的`mod2`内嵌套的`mod3

然而mod3无法看见mod2内的变量名,mod2无法看见mod1的变量名

mod1mod2只是变量名,引用带有属性的对象,而对象的属性可能引用其他带有属性的对象,可以依次取出对象的属性


重载模块

强制使模块代码重新载入并重新运行需要调用reload函数

  • 导人(importfrom语句)只有模块在流程中第一次导人时,加载和执行该模块的代码
  • 之后的导入只会使用已加载的模块对象,而不会重载或重新执行文件的代码
  • reload函数会强制已加载的模块的代码重新载入并重新执行,此文件中新的代码的赋值语句会在适当的地方修改现有的模块对象

重载模块可以无需停止整个程序而修改程序的一部分并立即看到对组件的修改效果,这样可以缩短开发的流程

Python是解释型语言,在执行程序导入时动态加载模块,重载进一步地提供性能优势,可以修改执行中的程序的一部分而不需终止

reload只能用在Python编写的模块,C这类语言编译后的拓展模块也可以在执行中动态加载,但无法重载

reload基础

reloadimportfrom不同的是

  • reload是Python中的函数,而不是语句
  • 传给reload的是成功导入了的模块对象,而不是变量名
  • reload在Python 3.0中位于模块之中,需要先导入再使用

当调用reload时,Python会重读模块文件的源代码,重新执行其顶层语句,然后在适当的地方修改模块对象而不是删除并重建模块对象,任何引用该模块对象的地方自动受到reload的影响

  • reload会在模块当前命名空间内执行模块文件的新代码

    重新执行模块文件的代码会覆盖其现有的命名空间,而非进行删除与重建

  • 文件中顶层赋值语句会使得变量名换成新值

    例如,重新执行的def语句会因重新赋值函数变量名而取代模块命名空间内该函数之前的版本

  • 重载会影响所有使用import读取了模块的客户端

    使用import的客户端需要通过.运算取出属性,在重载后,模块对象中属性会变成新值

  • 重载只会对以后使用from的客户端造成影响

    之前使用from来读取属性的客户端并不会受到重载的影响,那些客户端引用的依然是重载前所取出的旧对象

1
2
3
4
5
6
7
import module               # Initial import
...use module.attributes...
... # Now, go change the module file
...
from imp import reload # Get reload itself
reload(module) # Get updated exports
...use module.attributes...

导入一个模块,在文本编辑器内修改其源代码,然后将其重载

reload实例

1
2
3
4
5
# changer.py

message = "First version"
def priner():
print(message)

在文本编辑器中编写名为changer.py的模块文件,其中建立了两个变量名

1
2
3
4
% python
>>> import changer
>>> changer.printer()
First version

启动Python解释器,导入该模块,然后调用其导出的函数

1
2
3
4
5
...modify changer.py without stoppong Python...
% vi changer.py
message = "After editing"
def printer():
print('reloaded:', message)

在另一个窗口中编辑该模块文件

1
2
3
4
5
6
7
8
9
10
...back to the Python interpreter/program...

>>> import changer
>>> changer.printer() # No effect:uses loaded module
First version
>>> from imp import reload
>>> reload(changer) # Forces new cod eto load/run
<module 'changer' from 'changer.py'>
>>> changer.printer() # Runs the new version now
reloaded: After editing

回到Python窗口,重载该模块从而获得新的代码

reload返回模块对象

为什么要在意:模块重载

  • 在交互式提示符号下可以重载模块
  • 重载在较大系统也有用处,尤其是在重新启动整个程序的代价太大时
  • 重载在GUI工作中也很有用,组件的回调行为可以在GUI保持活动的状态下进行修改
  • 重载在Python作为C或C++程序的嵌入式语言时也有用处,C/C++程序可以请求重组其所执行的Python代码而无需停止

通常情况下重载使程序能够提供高度动态的接口,系统运作时通过Python定制产品而不用重新编译整个产品


本章习题

  1. 怎样创建模块

    要创建模块时,只需编写一个包含Python语句的文本文件就可以了,每个原代码文件都会自动成为模块,而且也没有语法用来声明模块,导人操作会把模块文件加载到内存中使其成为模块对象

    可以用C或Java这类外部语言编写代码来创建拓展模块

  2. from语句和import语句有什么关系

    from语句与import语句一样导入整个模块到内存中,但是会从被导入的模块中,复制一个或多个变量到from语句所在的作用域中

    这样可以让你直接使用被导入的变量名(name),而不是通过模块来使用变量名(module.name)

  3. reload函数和导人有什么关系

    默认,模块是每个进程只导入一次,reload函数会强制模块再次被导入

    基本上都是用于开发过程中选取模块源代码的新版本,或者用在动态定制的场景中

  4. 什么时候必须使用import,不能使用from

    当需要读取两个不同模块中的相同变量名时,就必须使用import,而不能用from

    因为你必须制定变量名所在模块,从而保证这两个变量名是独特的

  5. 请列举出from语句三种潜在陷阱

    from语句会让变量含义模糊(究竟是哪个模块定义的),通过reload调用时会有问题(变量名还是引用对象之前的版本),而且会破坏命名空间(可能悄悄覆盖正在作用域中使用的变量名)

    from *形式在多数情况下都很糟糕:它会严重地污染命名空间,让变量意义变得模糊


第二十三章 模块包

  • 介绍Python的包导入模型
  • 介绍新的导入模式

包导入基础

包导入就是导入指定目录路径,把计算机上的目录变成另一个Python命名空间,而属性则对应于目录中所包含的子目录和模块文件

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

1
2
import dir1.dir2.mod
from dir1.dir2.mod import x

import会和from语句中列出路径的名称彼此以点号相隔可以导入包

语句中的点号路径对应于机器上目录层次的路径,路径的最左边部分是sys.path模块搜索路径列表中的一个目录,通过这个路径可以获得文件mod.py

可以通过模块搜索路径找到包含这个点号路径的容器目录,容器目录需要添加在模块搜索路径中或是顶层文件的主目录

包和搜索路径设置

import语句中的目录路径只能是以点号间隔的变量,需要在模块搜索路径中设置包含点号路径的容器目录,可以将容器目录增加至PYTHONPATH系统变量或是.pth文件中

import语句以与平台不相关的方式提供了目录路径的写法

__init__.py包文件

使用包导入遵循的规则

  • 包导入语句的路径中的每个目录内都必须有__init__.py,否则导入包失败
  • 容器目录不需要__init__.py文件,会自动将其忽略
  • 容器目录必须列在模块搜索路径上

__init__.py可以像普通模块文件一样包含Python程序代码,也可以完全是空的

  • 作为Python的一种声明

    防止Python挑选出搜索路径上位置较前的相同名称的其他目录

  • 包的初始化

    Python首次导入某个目录时,会自动执行该目录下__ init__.py文件中的所有程序代码,这类文件是放置包内文件所需要初始化的代码的场所

  • 模块命名空间的初始化

    在包导入的模型中,脚本内的目录路径,在导入后会变成真实的嵌套对象路径

    __init__.py文件为目录所创建的模块对象提供命名空间

  • from *语句的行为

    __init__.py文件中,__ all__列表是指当包名称使用from *的时候,应该导入的子模块的名称单

    如果没有设定__all__,from *语句不会自动加载嵌套于该目录内的子模块,只加载该目录的__ init__.py文件中赋值语句定义的变量名,包括该文件中程序代码明确导入的任何子模块


包导入实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# dir1/__init__.py

print ('dir1 init')
x = 1

# dir1/dir2/__init__.py

print('dir2 init')
y = 2

# dir1/dir2/mod.py

print('in mod.py')
z = 3

其中dir1是模块搜索路径中一个目录的子目录,dir1的容器目录不需要__int__.py文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
>>> import dir1.dir2.mod		# First imports run init files
dir1 init
dir2 init
in mod.py

>>> import dir1.dir2.mod # Later imports do not
>>> from imp import reload # Needed in 3.0
>>> reload(dir1)
dir1 init
<module 'dir1' from '/test/dir1/__init__.py'>
>>> reload(dir1.dir2)
dir2 init
<module 'dir1.dir2' from '/test/dir1/dir2/__init__.py'>

>>> dir1
<module 'dir1' from '/test/dir1/__init__.py'>
>>> dir1.dir2
<module 'dir1.dir2' from '/test/dir1/dir2/__init__.py'>
>>> dir1.dir2.mod
<module 'dir1.dir2.mod' from '/test/dir1/dir2/mod.py'>
>>> dir1.x
1
>>> dir1.dir2.y
2
>>> dir1.dir2.mod.z
3

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

任何已导入的目录可以传递给reload强制该项目重新执行,reload可以接受点号路径名称重载嵌套的目录和文件

导入后import语句内的路径会变成脚本的嵌套对象路径,路径中的每个目录名称都会变成赋值为模块对象的变量,模块对象的命名空间则是由该目录内的__init__.py文件中所有赋值语句进行初始化的

包对应的from语句和import语句

1
2
3
4
5
6
7
8
>>> dir2.mod
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'dir2' is not defined
>>> mod.z
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'mod' is not defined

import和包一起使用时必须经常在程序中重新输入完整的路径

1
2
3
4
5
6
7
8
9
10
11
12
>>> from dir1.dir2 import mod		# Code path here only
dir1 init
dir2 init
in mod.py
>>> mod.z # Don't repeat path
3
>>> from dir1.dir2.mod import z
>>> z
3
>>> import dir1.dir2.mod as mod # Use shorter name
>>> mod.z
3

使用from导入包可以避免每次读取时都重新输入完整路径,并且在重新改变目录树结构时from只需要在程序代码中更新一次路径

import拓展功能import as提供类似功能


为什么要使用包导入

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

  • 包导入提供了程序文件的目录信息,可以作为组织工具轻松地找到文件,没有包导入时需要通过查看模块搜索路径才能找出文件

  • 包导入可以大幅简化PYTHONPATH.pth文件搜索路径设置,若将所有Python程序代码都存放在一个共同的根目录,搜索路径上只需要根目录一个接入点,即可通过包导入导入所有的程序代码

  • 包导入使想导入的文件更明确,从而解决了模糊性

  • 根据功能把文件组织成子目录,包导入会让模块扮演的角色更为明显,也使代码更具可读性

  • 1
    2
    3
    import utilities

    import database.client.utilities

三个系统的传说

实际中需要包导入的场合就是解决多个同名程序文件安装在同一个机器上时所引发的模糊性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
root/
system1/
__init__.py
utilities.py
main.py
other.py
system2/
__init__.py
utilities.py
main.py
other.py
system3
__init__.py
myfile.py

system1与system2包含了相同名称的模块文件:启动程序main.py与通用工具代码utilities.py

两个系统不需要配置模块搜索路径,运行main.py后同一主目录下的utilities.py在导入时最先被搜索到

当在第三个系统中导入前两个系统程序utilities.py时需要设置模块搜索路径,但是总会得到搜索路径上最先列出的目录内的utilities.py,而修改sys.path很容易出错

将三个系统放在共同根目录下并将根目录添加到搜索路径中,可以在系统三中以包导入任何一个系统的工具文件

  • 当各路径具有同名属性时使用import,不同时则可以使用from避免重复输入完整包的路径问题
  • __init__.py文件不需要在根目录内增加,只有在程序代码内需要
  • 系统三不需要放在根目录下,只有被导入的代码包需要
  • 两个初始系统的导入依然正常工作,因为会先搜索顶层文件所在的主目录

包相对导入

在包自身内部,包文件的导入可以使用和外部导入相同的路径语法,也可以使用特殊的包内搜索规则来简化导入语句

包内的导入可能相对于包而不是列出包导入路径

Python 3.0中的变化

包中导入在Python 3.0中的两个变化

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

相对导入基础知识

from语句使用.指定,需要位于同一包中的模块,而不是位于模块导入搜索路径上某处的模块

  • 使用from .来表示导入应该相对于外围的包,这样的导入将只是在包的内部搜索,并且不会搜索位于导入搜索路径(sys. path)上某处的同名模块,直接效果是包模块覆盖了外部的模块
  • Python3.0中,在一个包中导入默认是绝对的,在缺少任何特殊的.语法的时候,导入忽略了包含包自身并在sys .path搜索路径上的某处查找
1
from . import spam		# Relative to  this package

将位于与语句中给出的文件相同路径中名为spam模块导入

1
from .spam import name

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

为什么使用相对导入

当脚本在同名文件出现在模块搜索路径上许多地方时,可以解决模糊性

1
2
3
4
mypkg/
__Init__.py
main.py
strign.py

main.py试图导入string模块时将会导入mypkg目录下的string.py文件而无法导入Python标准库的string模块

Python 3.0中的相对导入解决方案

相对导入的作用域

模块查找规则总结

相对导入的应用

为什么要在意:模块包


本章习题

  1. 模块包目录内的__ init__ .py文 件有何用途

    __init__.py文件是用于声明和初始化模块包的

    第一次在进程中导入某目录时,Python会自动运行这个文件中的代码

    其赋值的变量会变成对应于该目录在内存中所创建的模块对象的属性

    它不是选用的:如果一个目录中没有包含这个文件的话,是无法通过包语法导人目录的

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

    通过from语句使用包,直接把包的变量名复制出来,或者使用import语句的as扩展功能,把路径改为较短的别名

    在这种情况下,路径只出现在了一个地方,就在fromimport语句中

  3. 哪些目录需要__init__.py文件

    import或from语句中所列出的每个目录都必须含有__init__.py文件

    其他目录则不需要包含这个文件,包括含有包路径最左侧组件的目录

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

    只有在你需要读取定义在一个以上路径的相同变量名时,才必须通过import来使用包,而不能使用from

    使用import路径可让引用独特化,然而from却让任何变量名只有一个版本

  5. from mypkg import spamfrom import spam有什么差别

    from mypkg import spam是绝对导入:mypkg的搜索掠过包路径并且mypkg位于sys. path中的一“个绝对目录中

    另一方面,from.import spam是相对导入:spam的查找是相对于该语句所在的包,然后才会去搜索sys.path


第二十四章 高级模块话题

  • 本章介绍数据隐藏、__future__模块、__name__变量、sys.path修改、列表工具、通过名称字符串来运行模块、过渡式重载等
  • 探索和总结模块设计
  • 相关陷阱和练习题

在模块中隐藏数据

Python导入模块时会导入其文件顶层所赋值的所有变量名,没有使变量名在模块内可见或不可见的概念

没有防止客户端修改模块内变量名的办法

最小化from *的破坏:_X__all__

from *语句导入模块时不会复制加有下划线的变量名,使对命名空间的破坏最小化

使用其他导入形式如import可以看见并修改带有下划线的变量名

1
__all__ = ["Error", "encode", "decode"]		# Export these only

在模块顶层把变量名的字符串列表赋值给__all__,from *语句只会把在__all__列表中的这些变量名复制出来

__all__列表对于其他导入形式同样无效


启用以后的语言特性

1
from __future__ import featurename

以拓展功能的方式使用新的的语言特性

这个语句一般出现在模块文件的顶端,以每个模块为基础,开启特殊的代码编译

可以出现在交互模式提示符下实验新的语言特性


混合用法模式:__name____main__

每个模块都有名为__name__的内置属性

  • 如果文件是以顶层程序文件执行,在启动时,__name__会设置为字符串__main__
  • 如果文件被导入,__name__会改设成的模块名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# runme.py

def tester():
print("It's Christmas in Heaven...")

if __name__ == '__main__': # Only when run
tester() # Not when import

% python

>>> import runme
>>> runme.tester()
It's Christmas in Heaven...

% python runme.py
It's Christmas in Heaven...

__name__变量充当使用模式标志,模块可以检测自己的__name__来确定是被执行还是被导入

使用__ name__测试最常见的是自我测试代码,在文件末尾添加__name__测试,把测试代码放在测试模块中,可以在客户端导入该文件并使用,也可以独立执行测试程序

使用__name__测试编写既可以作为命令行工具也可以作为工具库使用的文件,当文件独立执行时自动调用文件中的函数

__name__进行单元测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# min.py

print('I am:', __name__)

def minmax(test, *args):
res = args[0]
for arg in args[1:]:
if test(arg, res):
res = arg
return res

def lessthan(x, y): return x < y
def grtrthan(x, y): return x > y

if __name__ == '__main__':
print(minmax(lessthan, 4, 2, 1, 5, 6, 3)) # Self-test code
print(minmax(grtrthan, 4, 2, 1, 5, 6, 3))

% python min.py

I am: __main__
1
6

% python

I am: min
>>> min.minmax(min.lessthan, 's', 'p', 'a', 'm')
'a'

__name__检查区块内封装自我测试的调用,使其在文件作为顶层脚本执行时启动,而导入时则不会

Python开始加载文件时就创建了__name__变量并对其赋值,当顶层脚本执行这个文件时设置名称为__main__

使用带有__name__的命令行参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
"""
Various specialized string display formatting utilities.
Test me with canned self-test or command-line arguments.
"""

def commas(N):
"""
format positive integer-like N for display with
commas between digit groupings: xxx,yyy,zzz
"""
digits = str(N)
assert(digits.isdigit())
result = ''
while digits:
digits, last3 = digits[:-3], digits[-3:]
result = (last3 + ',' + result) if result else last3
return result

def money(N, width=0):
"""
format number N for display with commas, 2 decimal digits,
leading $ and sign, and optional padding: $ -xxx,yyy.zz
"""
sign = '-' if N < 0 else ''
N = abs(N)
whole = commas(int(N))
fract = ('%.2f' % N)[-2:]
format = '%s%s.%s' % (sign, whole, fract)
return '$%*s' % (width, format)
if __name__ == '__main__':
def selftest():
tests = 0, 1 # fails: -1, 1.23
tests += 12, 123, 12134, 12345, 123456, 1234567
tests += 2 ** 32, 2 ** 100
for test in tests:
print(commas(test))

print('')
tests = 0, 1, -1, 1.23, 1., 1.2, 3.14159
tests += 12.34, 12.344, 12.345, 12.346
tests += 2 ** 32, (2 ** 32 + .2345)
tests += 1.2345, 1.2, 0.2345
tests += -1.2345, -1.2, -0.2345
tests += -(2 ** 32), -(2 ** 32 + .2345)
tests += (2 ** 100), -(2 ** 100)
for test in tests:
print('%s [%s]' % (money(test, 17), test))
import sys
if len(sys.argv) == 1:
selftest()
else:
print(money(float(sys.argv[1]), int(sys.argv[2])))

程序定义了字符串格式化工具

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
%python formats.py 999999999 0
$999,999,999.00

%python formats.py -999999999 0
$-999,999,999.00

%python formats.py 123456789012345 0
$123,456,789,012,345.00

%python formats.py -123456789012345 25
$ -123,456,789,012,345.00

%python formats.py 123.456 0
$123.46

%python formats.py -123.454 0
$-123.45

%python formats.py
0
1
12
123
12,134
12,345
123,456
1,234,567
4,294,967,296
1,267,650,600,228,229,401,496,703,205,376

$ 0.00 [0]
$ 1.00 [1]
$ -1.00 [-1]
$ 1.23 [1.23]
$ 1.00 [1.0]
$ 1.20 [1.2]
$ 3.14 [3.14159]
$ 12.34 [12.34]
$ 12.34 [12.344]
$ 12.35 [12.345]
$ 12.35 [12.346]
$ 4,294,967,296.00 [4294967296]
$ 4,294,967,296.23 [4294967296.2345]
$ 1.23 [1.2345]
$ 1.20 [1.2]
$ 0.23 [0.2345]
$ -1.23 [-1.2345]
$ -1.20 [-1.2]
$ -0.23 [-0.2345]
$-4,294,967,296.00 [-4294967296]
$-4,294,967,296.23 [-4294967296.2345]
$1,267,650,600,228,229,401,496,703,205,376.00 [1267650600228229401496703205376]
$-1,267,650,600,228,229,401,496,703,205,376.00 [-1267650600228229401496703205376]

作为顶层脚本运行时测试并使用系统命令行上列出的参数运行一个定制的或传入的参数

1
2
3
4
5
6
7
8
>>> from formats import money, commas
>>> money(123.456)
'$123.46'
>>> money(-9999999.99, 15)
'$ -9,999,999.99'
>>> X = 99999999999999999999
>>> '%s (%s)' % (commas(X), X)
'99,999,999,999,999,999,999 (99999999999999999999)'

可以把这些工具作为库的部分导入到其他的环境中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>>> import formats
>>> help(formats)

Help on module formats:

NAME
formats

DESCRIPTION
Various specialized string display formatting utilities.
Test me with canned self-test or command-line arguments.

FUNCTIONS
commas(N)
format positive integer-like N for display with
commas between digit groupings: xxx,yyy,zzz

money(N, width=0)
format number N for display with commas, 2 decimal digits,
leading $ and sign, and optional padding: $ -xxx,yyy.zz

FILE
/formats.py

可以使用help函数研究其工具


修改模块搜索路径

模块搜索路径是一个目录列表,可以通过环境变量PYTHONPATH以及.pth路径文件进行定制

Python程序本身可以通过修改sys.path的内置列表来修改搜索路径

sys.path的设置方法只在修改的Python会话或程序中才会存续,程序结束后不会保留下来,PYTHONPATH.pth文件路径配置保存在系统中,程序结束后仍然存在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> import sys
>>> sys.path
['', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python36.zip', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/lib-dynload', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages']
>>> sys.path.append('/sourcedir') # Extend module search path
>>> import string # All imports search the new dir last

>>> sys.path = [r'/temp'] # Change module search path
>>> sys.path.append('/example') # For this process only
>>> sys.path
['/temp', '/example']
>>> import string
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'string'

sys.path在程序启动时初始化,之后可以随意对元素进行删除、附加和重设

sys.path做了修改会对导入的地方产生影响,从而在Python程序中动态配置搜索路径


Import语句和from语句的as拓展

importfrom语句可以拓展,将模块对象赋值为不同的变量名

语句:

1
import modulename as name

相当于:

1
2
3
import modulename
name = modulename
del modulename # Don't keep original name

使用as拓展后可以使用as之后的变量名引用该模块

1
from modulename import attrname as name

from语句也可以使用as拓展

1
2
3
4
5
6
7
8
9
import reallylongmodulename as name		# Use shorter nickname
name.func()

from module1 import utility as util1
from module2 import utility as util2
util1(); util2()

import dir1.dir2.mod as mod # Only list full path once
mod.func()

as拓展可以将较长的变量名替换为较短的变量名

as拓展可以避免不同模块中的相同变量名被覆盖

在包导入中可以将整个目录路径替换为较短的变量名


模块是对象:元程序

管理其他程序的程序为元程序,在其他系统之上工作

元程序也称为内省,因为程序能看见和处理对象的内部,还能用作创建程序的工具

1
2
3
4
M.name					# Qualify object
M.__dict__['name'] # Index namespace dictionary manually
sys.modules['M'].name # Index loaded-modules table manually
getattr(M, 'name') # Call built-in fetch function

取得M模块中的name属性

  • 可以使用.运算
  • 对模块的属性字典__dict__索引运算
  • sys.modules字典中导出已加载模块
  • 内置函数getattr以字符串名取出属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
"""
mydir.py: a module that lists the namespaces of other modules
"""

seplen = 60
sepchr = '-'

def listing(module, verbose=True):
sepline = sepchr * seplen
if verbose:
print(sepline)
print('name:', module.__name__, 'file:', module.__file__)
print(sepline)

count = 0
for attr in module.__dict__: # Scan namespace keys
print('%02d) %s' % (count, attr), end=' ')
if attr.startwith('__'):
print('<built-in name>') # Skip __file__, etc.
else:
print(getattr(module, attr)) # Same as .__dict__[attr]
count += 1

if verbose:
print(sepline)
print(module.__name__, 'has %d names' % count)
print(sepline)

if __name__ == '__main__':
import mydir
listing(mydir) # Self-test code: list myself

定制版本的内置函数dir,定义了listing函数,这个函数以模块对象为参数,打印该模块命名空间的格式化列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> import mydir
>>> help(mydir)

Help on module mydir:

NAME
mydir - mydir.py: a module that lists the namespaces of other modules

FUNCTIONS
listing(module, verbose=True)

DATA
sepchr = '-'
seplen = 60

FILE
/mydir.py

可以通过__doc__属性或help函数访问功能性信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
python mydir
------------------------------------------------------------
name: mydir file: /mydir.py
------------------------------------------------------------
00) __name__ <built-in name>
01) __doc__ <built-in name>
02) __package__ <built-in name>
03) __loader__ <built-in name>
04) __spec__ <built-in name>
05) __file__ <built-in name>
06) __cached__ <built-in name>
07) __builtins__ <built-in name>
08) seplen 60
09) sepchr -
10) listing <function listing at 0x1042056a8>
------------------------------------------------------------
mydir has 11 names
------------------------------------------------------------

导入并列出自己

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
>>> import mydir
>>> import tkinter
>>> mydir.listing(tkinter)
------------------------------------------------------------
name: tkinter file: /tkinter/__init__.py
------------------------------------------------------------
00) __name__ <built-in name>
01) __doc__ <built-in name>
02) __package__ <built-in name>
03) __loader__ <built-in name>
04) __spec__ <built-in name>
05) __path__ <built-in name>
06) __file__ <built-in name>
07) __cached__ <built-in name>
08) __builtins__ <built-in name>
09) enum <module 'enum' from '/enum.py'>
10) sys <module 'sys' (built-in)>
11) _tkinter <module '_tkinter' from '/lib-dynload/_tkinter.cpython-36m-darwin.so'>
12) TclError <class '_tkinter.TclError'>
13) constants <module 'tkinter.constants' from '/constants.py'>
14) NO 0
15) FALSE 0
16) OFF 0
17) YES 1
18) TRUE 1
19) ON 1
20) N n
21) S s
22) W w
23) E e
24) NW nw
25) SW sw
26) NE ne
27) SE se
28) NS ns
29) EW ew
30) NSEW nsew
31) CENTER center
32) NONE none
33) X x
34) Y y
35) BOTH both
36) LEFT left
37) TOP top
38) RIGHT right
39) BOTTOM bottom
40) RAISED raised
41) SUNKEN sunken
42) FLAT flat
43) RIDGE ridge
44) GROOVE groove
45) SOLID solid
46) HORIZONTAL horizontal
47) VERTICAL vertical
48) NUMERIC numeric
49) CHAR char
50) WORD word
51) BASELINE baseline
52) INSIDE inside
53) OUTSIDE outside
54) SEL sel
55) SEL_FIRST sel.first
56) SEL_LAST sel.last
57) END end
58) INSERT insert
59) CURRENT current
60) ANCHOR anchor
61) ALL all
62) NORMAL normal
63) DISABLED disabled
64) ACTIVE active
65) HIDDEN hidden
66) CASCADE cascade
67) CHECKBUTTON checkbutton
68) COMMAND command
69) RADIOBUTTON radiobutton
70) SEPARATOR separator
71) SINGLE single
72) BROWSE browse
73) MULTIPLE multiple
74) EXTENDED extended
75) DOTBOX dotbox
76) UNDERLINE underline
77) PIESLICE pieslice
78) CHORD chord
79) ARC arc
80) FIRST first
81) LAST last
82) BUTT butt
83) PROJECTING projecting
84) ROUND round
85) BEVEL bevel
86) MITER miter
87) MOVETO moveto
88) SCROLL scroll
89) UNITS units
90) PAGES pages
91) re <module 're' from '/re.py'>
92) wantobjects 1
93) TkVersion 8.5
94) TclVersion 8.5
95) READABLE 2
96) WRITABLE 4
97) EXCEPTION 8
98) _magic_re re.compile('([\\\\{}])')
99) _space_re re.compile('([\\s])', re.ASCII)
100) _join <function _join at 0x104783400>
101) _stringify <function _stringify at 0x104783488>
102) _flatten <built-in function _flatten>
103) _cnfmerge <function _cnfmerge at 0x104783598>
104) _splitdict <function _splitdict at 0x104783620>
105) EventType <enum 'EventType'>
106) Event <class 'tkinter.Event'>
107) _support_default_root 1
108) _default_root None
109) NoDefaultRoot <function NoDefaultRoot at 0x1047836a8>
110) _tkerror <function _tkerror at 0x1047838c8>
111) _exit <function _exit at 0x104783950>
112) _varnum 0
113) Variable <class 'tkinter.Variable'>
114) StringVar <class 'tkinter.StringVar'>
115) IntVar <class 'tkinter.IntVar'>
116) DoubleVar <class 'tkinter.DoubleVar'>
117) BooleanVar <class 'tkinter.BooleanVar'>
118) mainloop <function mainloop at 0x1047839d8>
119) getint <class 'int'>
120) getdouble <class 'float'>
121) getboolean <function getboolean at 0x1047b86a8>
122) Misc <class 'tkinter.Misc'>
123) CallWrapper <class 'tkinter.CallWrapper'>
124) XView <class 'tkinter.XView'>
125) YView <class 'tkinter.YView'>
126) Wm <class 'tkinter.Wm'>
127) Tk <class 'tkinter.Tk'>
128) Tcl <function Tcl at 0x1047b8730>
129) Pack <class 'tkinter.Pack'>
130) Place <class 'tkinter.Place'>
131) Grid <class 'tkinter.Grid'>
132) BaseWidget <class 'tkinter.BaseWidget'>
133) Widget <class 'tkinter.Widget'>
134) Toplevel <class 'tkinter.Toplevel'>
135) Button <class 'tkinter.Button'>
136) Canvas <class 'tkinter.Canvas'>
137) Checkbutton <class 'tkinter.Checkbutton'>
138) Entry <class 'tkinter.Entry'>
139) Frame <class 'tkinter.Frame'>
140) Label <class 'tkinter.Label'>
141) Listbox <class 'tkinter.Listbox'>
142) Menu <class 'tkinter.Menu'>
143) Menubutton <class 'tkinter.Menubutton'>
144) Message <class 'tkinter.Message'>
145) Radiobutton <class 'tkinter.Radiobutton'>
146) Scale <class 'tkinter.Scale'>
147) Scrollbar <class 'tkinter.Scrollbar'>
148) Text <class 'tkinter.Text'>
149) _setit <class 'tkinter._setit'>
150) OptionMenu <class 'tkinter.OptionMenu'>
151) Image <class 'tkinter.Image'>
152) PhotoImage <class 'tkinter.PhotoImage'>
153) BitmapImage <class 'tkinter.BitmapImage'>
154) image_names <function image_names at 0x1047c2268>
155) image_types <function image_types at 0x10f9c7510>
156) Spinbox <class 'tkinter.Spinbox'>
157) LabelFrame <class 'tkinter.LabelFrame'>
158) PanedWindow <class 'tkinter.PanedWindow'>
159) _test <function _test at 0x10f9c7598>
------------------------------------------------------------
tkinter has 160 names
------------------------------------------------------------

可以直接把模块作为对象传入到这个文件的函数中


用名称字符串导入模块

import语句中的名称既是引用载入模块对象的一个变量名,也从字面上标识了该外部对象

1
2
3
4
5
6
7
8
9
10
11
>>> import "string"
File "<stdin>", line 1
import "string"
^
SyntaxError: invalid syntax

>>> x = "string"
>>> import x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'x'

importfrom语句中的模块名是一个变量名称,无法载入以字符串形式给出的模块名

Python会尝试寻找变量名所对应的Python文件,而非变量名所引用的字符串对象

1
2
3
4
>>> modname = "string"
>>> exec("import " + modname) # Run a string of code
>>> string # Imported in this namespace
<module 'string' from '/string.py'>

import语句构建为一个字符串,并将其传递给exec内置函数可以动态加载一个模块

exec函数及eval函数编译一个代码字符串,并将其传递给Python解释器执行

exec默认运行当前当前作用域中的代码,可以通过传入可选的命名空间字典更加具体地应用

exec每次运行时都需要编译import语句所以运行速度较慢

1
2
3
4
>>> modname = "string"
>>> string = __import__(modname)
>>> string
<module 'string' from '/string.py'>

内置__import__函数接受一个名称字符串并返回一个模块对象


过渡性模块重载

重载可以修改程序代码而不需要停止或重新启动该程序

1
2
3
4
5
6
7
8
9
10
# A.py

import B # Not reloaded when A is
import C # Just an import of an already loaded module

% python

>>> import A
>>> from imp import reload
>>> reload(A)

Python重载传入的模块对象,模块对象中的导入语句若在此前已经运行则只会获取已经载入的模块对象

可以使用多次reload调用来独立地更新子部分或在父模块中添加reload调用自动重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
"""
reloadall.py: transitively reload nested modules
"""

import types
from imp import reload # from required in 3.0

def status(module):
print('reloading ' + module.__name__)

def transitive_reload(module, visited):
if not module in visited: # Trap cycles,duplicates
status(module) # Reload this module
reload(module) # And visit children
visited[module] = None
for attrobj in module.__dict__.values(): # For all attrs
if type(attrobj) == types.ModuleType: # Recur if module
transitive_reload(attrobj, visited)

def reload_all(*args):
visited = {}
for arg in args:
if type(arg) == types.ModuleType:
transitive_reload(arg, visited)

if __name__ == '__main__':
import reloadall # Test code:reload myself
reload_all(reloadall) # Should reload this,types

通过递归的扫描模块的__dict__属性并检查每一项的type找到要重载的嵌套模块

visited用来在导入是递归或冗余的时候避免循环

types模块直接为内置类型预定义type结果

1
2
3
4
5
% python reloadall.py
reloading reloadall
reloading types
reloading functools
reloading collections.abc

文件独立运行时,需要先导入自己,然后测试自己

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
>>> from reloadall import reload_all
>>> import os, tkinter
>>> reload_all(os)
reloading os
reloading abc
reloading sys
reloading errno
reloading stat
reloading posixpath
reloading genericpath

>>> reload_all(tkinter)
reloading tkinter
reloading enum
reloading sys
reloading _tkinter
reloading tkinter.constants
reloading re
reloading sre_compile
reloading _sre
reloading sre_parse
reloading functools
reloading _locale
reloading copyreg

使用时导入reload_all并将一个已载入的模块的名称传递给它

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# a.py

import b
X = 1

# b.py

import c
Y = 2

# c.py
Z = 3

% python

>>> import a
>>> a.X, a.b.Y, a.b.c.Z
(1, 2, 3)

#Change all three files' assignment values and save

>>> from impo import reload
>>> reload(a) # Normal reload is top level only
<module 'a' from '/a.py'>
>>> a.X, a.b.Y, a.b.c.Z
(111, 2, 3)

>> from reloadall import reload_all
reload_all(a)
reloading a
reloading b
reloading c
>>> a.X, a.b.Y, a.b.c.Z
(111, 222, 333)

除非使用过渡性工具,否则重载不会对嵌套的导入文件进行修改


模块设计理念

模块的操作环境包含变量、函数、类以及其他的模块,函数和类有自己的本地变量

模块设计方面的折中考虑:需要思考那些函数要放进模块、模块通信机制

模块的通用概念

  • 总是在Python的模块内编写代码

    没有办法写出不在某个模块之内的程序代码,在交互模式提示符下输入的程序代码其实是存在于内置模块__main__之内

    交互模式提示符独特之处就在于程序是执行后就立刻丢弃,以及表达式结果是自动打印的

  • 模块耦合要降到最低:全局变量

    就像函数一样,如果编写成闭合的盒子,模块运行得最好

    编写原则就是除了导入的函数和类,模块应该尽可能和其他模块的全局变量无关

  • 最大化模块的黏合性:统一目标

    可以通过最大化模块的黏合性来最小化模块的耦合性

    如果模块的所有元素都享有共同的目的,就不太可能依赖外部的变量名

  • 模块应该少去修改其他模块的变量

    可以使用另一个模块定义的全局变量,但是修改另一个模块内的全局变量通常会出现设计问题

    应该试着通过函数参数返回值这类机制去传递结果而不是进行跨模块的修改

    被修改的模块中全局变量的值会变成依赖于其他文件内的赋值给全局变量的值,而模块会变得难以理解和再利用


模块陷阱

本节中,我们要看一看会让Python初学者生活多点乐趣的常见的极端案例

顶层代码的语句次序的重要性

当模块首次导入或重载时,Python会从头到尾执行语句

作为原则,如果需要把立即执行的代码和def一起混用,就要把def放在文件前面,把顶层代码放在后面,保证代码运行时def已定义并赋值

前向引用相关含义

  • 在导入时,模块文件顶层的程序代码(不在函数内)会在Python运行到时立刻执行,因此程序代码无法引用文件后面位置赋值的变量名
  • 位于函数主体内的代码直到函数被调用后才会运行.因为函数内的变量名在函数实际执行前都不会解析,通常可以引用文件内任意地方的变量
1
2
3
4
5
6
7
8
9
10
11
func1()					# Error:"func1" not yet assigned

def func1():
print(func2()) # Okay:"func2" looked up later

func1() # Error:"func2" not yet assigned

def func2():
return "Hello"

func1() # Okay:"func1" and "func2" assigned

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

from赋值变量名,而不是连接

from语句是在导入者的作用域内对变量名的赋值语句,也就是先对引用对象拷贝,再对拷贝的对象赋值,不会连接到模块中的变量名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# nested1.py
X = 99
def printer(): print(X)

# nested2.py
from nested1 import X, printer # Copy names out
X = 88 # Changes my "X" only!
printer() # nested1's X is still 99

% python nested2.py
99

# nested3.py
import nested1 # Get module as a whole
nested1.X = 88 # OK:change nested1's X
nested1.printer()

% python nested3.py
99

使用from导入模块属性,在导入者内修改变量名只会重设该变量名引用的本地作用域副本对象的绑定值

使用import获得整个模块,赋值.运算的变量名会修改被导入模块中的变量名

.运算把Python定向到模块对象内的变量名而不是导入者的变量名

from *会让变量语义模糊

from module import *可能会意外覆盖作用域内已使用的变量,而且很难确认变量来自何处

1
2
3
4
5
6
>>> from module1  import *		# Bad:may overwrite my names silently
>>> from module2 import * # no way to tell what we get!
>>> from module2 import *
>>> ...

>>> func() # Huh?

在三个模块上使用from *后就没有办法知道简单的函数调用真正含义,除非搜索这三个外部模块文件

解决的办法是不要这样做,应该在from语句中明确列出想要的属性,而且限制在每个文件中最多只有一个被导入的模块使用from *语句.这样未定义的变量一定来自from *所导入的模块

reload不会影响from导入

from在执行时会复制模块属性并赋值给变量名,所以不会连接到变量名的那个模块

1
2
3
4
5
6
7
8
9
10
11
from module import X		# X may not reflect any module reloads!
...
from imp import relaod
reload(module) # Changes module,but not my names
X # Still references old object

import module # Get module, not names
...
from imp import reload
relaod(module) # Changes module in-place
module.X # Get current X:reflects module reloads

由于from语句的机制,重载被导入者对于使用from导入者的变量名没有影响

使用import语句及.运算会连接模块内的变量名,这样就会找到模块重载后变量名的新的绑定值

reload、from以及交互模式测试

通常不应该通过reloadfrom来启动程序,可以使用reloadimport 或者其他方式启动程序

1
2
3
4
5
>>> from module import function
>>> function(1, 2, 3)

>>> from impo import reload
>>> reload(module)

启动一个交互模式会话,通过from加载并测试模块

from语句赋值的是变量名function而不是module,重载是行不通的

1
2
3
4
5
from imp import relaod
import module
relaod(module)
from module import function # Or give up adn use module.function()
function(1, 2, 3)

可以重新执行from语句或者使用import语句和.运算再重载

递归形式的from导入无法工作

导入会从头到尾执行一个文件的语句,但是使用相互导入的模块时需要十分小心,当被导入的模块递归导入导入者时Python会避免重新执行以防止死循环,所以导入者模块的命名空间此时还不完整,有些变量名可能还不存在

递归导入时使用import可以在在取出整个模块后稍后使用.运算读取还未赋值的变量名,使用from只能读取在模块中已经赋值的变量名

有两种方式避开这个陷阱

  • 小心设计,通常可以避免这种导入循环:最大化模块的聚合性,同时最小化模块间的耦合性,是一个很好的开始
  • 如果无法完全断开循环,就要使用import.运算而不是from,将模块变量名的读取放在后边,要么就是在函数中,或者在文件末尾附近去执行from,以延迟其执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# recur1.py
X = 1
import recur2 # Run recur2 now if it doesn't exist
Y = 2

# recur2.py
from recur1 import X # OK: "X" already assigned
from recur1 import Y # Error:"Y" not yet assigned

% python
>>> import recur1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/recur1.py", line 2, in <module>
import recur2
File "/recur2.py", line 2, in <module>
from recur1 import Y
ImportError: cannot import name 'Y'

recur1给变量名X赋值,然后在赋值变量名Y之前导入recur2,这时recur2可以用importrecur1整个取出,但是如果是用from只能看见变量名X,变量名Y是在导入recur1后赋值的,此时还不存在,所以会产生错误


本章习题

  1. 模块顶层以下划线开头的变量名的重要性是什么

    当使用from *语句形式导入时,以单个下划线开头的模块顶层变量名不会被复制到执行导入的作用域中

    这类变量名可通过import或者普通的from语句形式来导入

  2. 当模块的__name__变量是字符串__main__时, 代表了什么意义

    如果模块的__name__变量是字符串__main__,代表了该文件是作为顶层脚本运行的,而不是被另一个文件所导入的

    也就是说,这个文件作为程序在使用,而不是一个库

  3. 如果用户通过交互模式输入模块的变量名进行测试,该怎样进行导人

    用户输入脚本时通常作为字符串,要通过字符串类型的变量名导入所引用的模块,可以创建import语句并通过exec执行,或把字符串名传给__ import__函数进行调用

  4. 改变sys.path和设置PYTHONPATH来修改模块搜索路径有什么不同

    修改sys.path只会影响一个正在运行的程序,是暂时的,当程序结束时,修改就会消失

    PYTHONPATH设置是存在于操作系统中的,机器上所有程序都会使用,而且对这些设置的修改在程序离开后还会保存

  5. 如果模块__future__可以导入未来,那可以导入过去吗

    不行,无法在Python中导入过去

    可以安装这门]语言的旧版本

CATALOG
  1. 1. 第二十一章 模块:宏伟蓝图
    1. 1.1. 为什么使用模块
    2. 1.2. Python程序架构
      1. 1.2.1. 如何组织一个程序
      2. 1.2.2. 导入和属性
      3. 1.2.3. 标准库模块
    3. 1.3. import如何工作
      1. 1.3.1. 搜索
      2. 1.3.2. 编译(可选)
      3. 1.3.3. 运行
    4. 1.4. 模块搜索路径
      1. 1.4.1. 配置搜索路径
      2. 1.4.2. 搜索路径的变动
      3. 1.4.3. sys.path
      4. 1.4.4. 模块文件选择
      5. 1.4.5. 高级的模块选择概念
      6. 1.4.6. 第三方工具:distutils
    5. 1.5. 本章习题
  2. 2. 第二十二章 模块代码编写基础
    1. 2.1. 模块的创建
    2. 2.2. 模块的使用
      1. 2.2.1. import语句
      2. 2.2.2. from语句
      3. 2.2.3. from *语句
      4. 2.2.4. 导入只发生一次
      5. 2.2.5. import和from是赋值语句
      6. 2.2.6. 文件间变量名的改变
      7. 2.2.7. import和from的对等性
      8. 2.2.8. from语句潜在的陷阱
        1. 2.2.8.1. 何时使用import
    3. 2.3. 模块命名空间
      1. 2.3.1. 文件生成命名空间
      2. 2.3.2. 属性名的点号运算
      3. 2.3.3. 导入和作用域
      4. 2.3.4. 命名空间的嵌套
    4. 2.4. 重载模块
      1. 2.4.1. reload基础
      2. 2.4.2. reload实例
      3. 2.4.3. 为什么要在意:模块重载
    5. 2.5. 本章习题
  3. 3. 第二十三章 模块包
    1. 3.1. 包导入基础
      1. 3.1.1. 包和搜索路径设置
      2. 3.1.2. __init__.py包文件
    2. 3.2. 包导入实例
      1. 3.2.1. 包对应的from语句和import语句
    3. 3.3. 为什么要使用包导入
      1. 3.3.1. 三个系统的传说
    4. 3.4. 包相对导入
      1. 3.4.1. Python 3.0中的变化
      2. 3.4.2. 相对导入基础知识
      3. 3.4.3. 为什么使用相对导入
        1. 3.4.3.1. Python 3.0中的相对导入解决方案
      4. 3.4.4. 相对导入的作用域
      5. 3.4.5. 模块查找规则总结
      6. 3.4.6. 相对导入的应用
      7. 3.4.7. 为什么要在意:模块包
    5. 3.5. 本章习题
  4. 4. 第二十四章 高级模块话题
    1. 4.1. 在模块中隐藏数据
      1. 4.1.1. 最小化from *的破坏:_X和__all__
    2. 4.2. 启用以后的语言特性
    3. 4.3. 混合用法模式:__name__和__main__
      1. 4.3.1. 以__name__进行单元测试
      2. 4.3.2. 使用带有__name__的命令行参数
    4. 4.4. 修改模块搜索路径
    5. 4.5. Import语句和from语句的as拓展
    6. 4.6. 模块是对象:元程序
    7. 4.7. 用名称字符串导入模块
    8. 4.8. 过渡性模块重载
    9. 4.9. 模块设计理念
    10. 4.10. 模块陷阱
      1. 4.10.1. 顶层代码的语句次序的重要性
      2. 4.10.2. from赋值变量名,而不是连接
      3. 4.10.3. from *会让变量语义模糊
      4. 4.10.4. reload不会影响from导入
      5. 4.10.5. reload、from以及交互模式测试
      6. 4.10.6. 递归形式的from导入无法工作
    11. 4.11. 本章习题