Skip to main content

模块

模块的简介​

为什么使用模块​

  1. 代码重用
  2. 系统命名空间的划分
  3. 实现共享服务和数据

Python程序构架​

model-map

import如何工作​

导入是运行时一个运算​

第一次导入时

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

后续调用直接在内存中 拿到定义好的对象就行了

如何找到模块文件​

  1. 找程序的主目录(即入口程序的目录)
  2. 找Python的PATH
  3. 标准库目录
  4. 找任何.pth文件的内容(配置目录文件)

导入的文件扩展名问题​

  1. 找.py源代码文件
  2. .pyc 编译后的字节码文件(比较py和pyc的大小和时间戳,如果一样就运行pyc不一样就重新生成pyc)
  3. Linux .so Win .dll .pyd 编译好的扩展文件
  4. 压缩文件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,点号可以多层运算

导入和作用域​

moda.py
X = 88
def f():
global X
X = 99
modb.py
X = 11

import moda
moda.f()
print(X, moda.X)

moda.f修改moda的X,moda.f的全局总用语移动式其所在文件,上述运行为 11 99

  • 函数绝对无法看见其他函数内的名称,除非物理上属于同一函数内
  • 模块程序代码绝对无法看到其他模块内的名称,除非被显示的导入

作用域绝对不会被函数调用或模块导入影响

命名空间的嵌套​

导入不会使命名空间发生向上的嵌套买单时会发生向下的嵌套

mod3
X = 3
mod2
X = 2
import mod3

print(x, end='')
print(mod3.X)
mod1
X = 1

import mod2

print(X, end = '')
print(mod2.X, end = '')
print(mod2.mod3.X, end = '')
output
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.py dir1在sys.path中 \ns\dir2\sub\mod2.py dir2在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章