模块
模块的简介
为什么使用模块
- 代码重用
- 系统命名空间的划分
- 实现共享服务和数据
Python程序构架
import如何工作
导入是运行时一个运算
第一次导入时
- 找到模块文件
- 编译成位码
- 执行模块的代码来创建其所定义的对象
后续调用直接在内存中 拿到定义好的对象就行了
如何找到模块文件
- 找程序的主目录(即入口程序的目录)
- 找Python的PATH
- 标准库目录
- 找任何.pth文件的内容(配置目录文件)
导入的文件扩展名问题
- 找.py源代码文件
- .pyc 编译后的字节码文件(比较py和pyc的大小和时间戳,如果一样就运行pyc不一样就重新生成pyc)
- Linux .so Win .dll .pyd 编译好的扩展文件
- 压缩文件JAVA的类等
模块基础
模块的使用
- import 会整体读取一个模块,之后要用
.才能获取其中的名称(module.def)把另一个文件的对象引用过来 - 而from将从模块中取出或复制特定的方法(直接应用方法)赋值变量名
- from * 讲整个模块所有方法复制过来
- 导入是个开销很大的操作,每个进程只导入一次 模块只导入一次
import simple
simple.spam # 1
simple.sapm = 5
simple.spam # 5
import simple
simple.spam # 5
导入操作实际上是隐性的赋值操作
>>>from small import x, y # 将small里的x, y赋值给当前模块里的同名对象
>>>x = 42 # 只改变当前作用域
>>>y[0] = 42
>>>import samll # 将整个模块赋值给一个变量名samll变量
>>>small.x
1
>>>small.y # 列表是引用型的变量,只修改了一个名称
[42, 2]
>>>samll.x = 42 # 这是一个很危险的操作,会改变会修改一的对象,实际修改了另一个文件中的全局变量,同时两个模块中的这一名称会引用同一个修改后的对象
模块命名空间
模块是变量名的封装,模块就是一个文件,python建立一个模块对象,以包含文件内赋值的所有名称。模块就是命名空间,存在于一个模块内的名称被称为模块对象的属性。
文件产生命名空间
- 模块语句会在首次导入的时候执行,即建立空的模块对象,逐一执行文件内的语句
- 顶层的赋值语句会创建模块属性,建立模块对象的属性
- 模块的命名空间能通过
__dict__&dir(M)获取。由导入建立的模块对象是字典 - 模块是一个独立的作用域(局部变量就是全局变量)。模块顶层的名称遵循和函数内名称相同的引用/赋值规则,但局部作用域和全局作用域相同。
属性名的点号运算
即属性获取object.attribute访问任何拥有属性的对象的属性
点号运算就是表达式,它会返回和对象相关联的属性名的值。l.append会返回和该列表相关联的append方法对象。
x.y在当前作用域中搜索x,然后搜索对象x中的属性y,点号可以多层运算
导入和作用域
X = 88
def f():
global X
X = 99
X = 11
import moda
moda.f()
print(X, moda.X)
moda.f修改moda的X,moda.f的全局总用语移动式其所在文件,上述运行为 11 99
- 函数绝对无法看见其他函数内的名称,除非物理上属于同一函数内
- 模块程序代码绝对无法看到其他模块内的名称,除非被显示的导入
作用域绝对不会被函数调用或模块导入影响
命名空间的嵌套
导入不会使命名空间发生向上的嵌套买单时会发生向下的嵌套
X = 3
X = 2
import mod3
print(x, end='')
print(mod3.X)
X = 1
import mod2
print(X, end = '')
print(mod2.X, end = '')
print(mod2.mod3.X, end = '')
2 3
1 2 3
重新加载模块
- 当模块第一次在进程中被导入时,菜价在和执行该模块的代码
- 之后的导入之后使用已加载的模块对象
- reload函数会强制已加载的模块的代码重新载入并执行。文件中新的代码的赋值语句会在原位置修改现有的模块对象。
只支持Python模块
- reload是一个函数,不是语句
- reload传入的参数是一个已经存在的模块对象,而不是一个新的名称
- reload在Python3.x在模块中必须导入才能使用(import module/form imp import reload)
- reload会在模块当前命名空间内执行模块文件的新代码(会覆盖命名空间)
- 文件中顶层赋值语句会将名称替换成新的值
- 重新加载会影响所有使用import读取了模块的用户程序(import的.号运算会发现取出来的属性变了)
- 重新加载只会对以后使用from的用户程序造成影响(from会使酒用户用的还是之前加载的旧对象)
- 重新加载只适用于单一的模块。除非使用了传递式地应用重新加载的代码工具
模块和命名空间
import 可以把模块连接起来 每个文件都有自包含的命名空间 只对自己可见 通过import可以让别的模块看见 import语句每个进程会运行一次 生成自己的命名空间
object.attribute 使用
form 把别的模块中的变量copy过来 可以直接使用 重复定义变量会被覆盖
import/from 以后更改或者重新导入都不会再一次运行模块
重新加载模块
import imp
imp.relode(x)
exec(open('x.py').read())直接从交互页面运行模块文件中保存的代码 就像粘贴一样 缺点 会覆盖现在正在使用的变量
模块高级话题
模块设计概念
- **最小化模块耦合:全局变量。**除了主动导入的函数和类外,模块应该尽可能独立于其他模块内使用的全局变量。模块唯一应该与外界共享的应该是它使用和创造的工具。
- **最大化模块内聚:同一目标。**模块内所有组件都有一个共同的目标
- 模块尽可能不去更改其他模块的变量
模块中的数据隐藏
在名称前加_这个变量就不会在from *导入的时候显示出来,但是仍然能被mod._x来访问修改
用__all__可以声明一个可以被from *显示出来的列表,在导入的时候只能显示出列表里声明的变量
启用未来语言特性:__future__
from __future__ import featurename
作为程序第一行使用
在导入后对__future__使用dir函数可以查看条目,从而实现代码的向前兼容
混合使用模式__name__和_\main__
__name__内置属性:
- 如果文件作为顶层程序文件执行,在启动时__name__就会被设置成字符串"__main__"
- 如果文件被导入,__name__就会改设成客户程序所了解的程序名
常用在模块末尾
if __name__ == '__main':来测试模块
修改模块搜索路径
sys.path是一个列表,python的path存在列表里,可以直接用append函数来向里面添加path但是是写到缓存里的,重启程序后失效
import语句与from语句的as扩展
用as就是将import和from进来的对象名,定义成一个新的对象名
import mod1 as a
a.func()
from mod1 import func as b
b()
模块即使对象
可以编写程序来管理其他程序,这类管理程序叫做元程序。
运行代码字符串
exec会变异一个代码字符串,并且传给Python解释器执行。
modname = 'xxx'
exec('import' + modname)
exec的缺点是每次运行必须变异improt语句,可以用compile内置工具便以为字节码提速。
递归重载器
P752
模块陷阱
- 避免模块名称冲突
- 顶层代码中的次序问题
- from复制名称,而不是链接 from过来的变量是直接复制过来的而不是链接导入模块中的那一个
- from *会让变量含义模糊化
- reload不能用做与from导入,而且不能递归reload
- 递归形式的from导入可能无法工作 可能导致死循环
模块包
一般情况下含有__init__.py的文件夹就叫模块包,除3.3后
在import 语句中列举简单的文件名的地方
import
import dir1.dir2.mod #绝对路径,绝对导入 详见相对导入
from语句也是一样
from dir1.dir2.mod import x
__init__.py文件
包导入语句的每个目录内都必须有__init__.py文件,否则包导入会失败。
目录结构:
dir0/dir1/dir2/mod.py
在dir0下的py模块想导入mod.py:
import dir1.dir2.mod
必须遵循下列规则:
- dir1和dir2中必须含有一个__init__.py文件
- dir0是容器,不需要__init__.py文件;如果有则会被忽略
- dri0必须列在模块搜索路径的sys.path列表中
dir0必须是自动搜索路径的一部分,可在PYTHONPATH或.pth,或手动修改sys.path
__init__.py文件可以用作包初始化的钩子,将目录声明成一个python包,替目录生成一个模块命名空间以及在目录导入时实现from * 语句的行为角色。
- 包的初始化:首次导入某个文件会自动执行__init__.py里面的所有代码
- 模块使用的声明:声明一个路径为python包
- 模块命名空间的初始化
- form*语句的行为
包的from和import
如果每次都想用dir2下的mod用import在使用的时候必须加上dir1.dir2.mod
这个时候可以直接用fromfrom dir1.dir1 import mod这时直接用mod就可以
包相对导入
- 相对导入只支持from,import都是绝对导入
- 只支持包内部的导入
- 用于从包自身的内部,包文件导入同一个包中的内容时。
- 可以使用和外部导入相同的完整路径语法
- 使用特殊的语法只支持python3.X
以点号开头的导入
- 在from语句中使用以点号开头的模块名,表示导入只相对于外围的包
- 强制相对导入
- 相当于linux里的.号 可以用多个点来表示上级文件夹 不过这里的一个点表示本级
不以点号开头的导入
- 在python 2.x中默认是先相对再绝对,在代码第一行加
from __future__import absolute_import来开启强制绝对导入 - 在开启的2.x和3.x中略过模块相对导入
命名空间包
- 是在python 3.3版本以后引入的
- 所有目录必须不包含__init__.py文件
- 导入算法:dir在sys.path中如果找到文件夹dir\spam sapm会被记录下来,命名空间包的创建即刻发生,新的命名空间有__path__属性
\ns\dir1\sub\mod1.pydir1在sys.path中\ns\dir2\sub\mod2.pydir2在sys.path中 都不含有__init__.py文件
import sub
sub # module 'sub' (namespace)
sub.__path__ # \ns\dir1\sub\,\ns\dir2\sub
from sub import mod1
import sub.mod2
mod1
sub.mod2
更多详见python学习手册24章