模块化
一般来说,编程语言中,库,包,模块是同一种概念,是代码组织方式
python只有模块的对象类型,但是为了模块化组织模块的便利,提供了包的概念
模块module,指的是Python的源代码文件。
包package,指的是模块组织在一起的包名同名的目录及其相关文件
导入语句
import模块,允许写成一行,完全导入
import ... as ... 模块别名
impor语句,会找到指定的模块,加载和初始化它,生成模块对象,找不到,抛出ImportError异常
在Import所在的作用域的局部命名空间中,增加和上一步创建的对象关联
import后只能出现模块类型,包或者模块
总结:
import语句,只导入顶级模块,其名称会加入到本地名词空间,并绑定到其模块对象
导入非顶级模块,只将其顶级模块的名称加入本地命名空间,导入的模块必须使用完全限定名称来访问。
如果使用了as,as后的名称直接绑定到导入的模块对象,该名称加入本地名词空间
from 模块 import 后跟较多,模块,类,函数,变量都可以
from ... import ... as ...
from后的模块不会填入名词空间,
from pathlib import Path, PosixPath #在当前名词空间导入该模块指定的成员 print(dir()) #[..., 'Path','PosixPath']
from functools import * #在当前名词空间导入该模块所有的公共成员或指定成员 print(dir()) ##'WRAPPER_ASSIGNMENTS', 'WRAPPER_UPDATES',..., 'update_wrapper', 'wraps'
from os.path import exists #加载,初始化os,os.path模块,exists加入本地名词空间 if exists('test.txt'): print('exists') else: print('not found') print(dir()) print(exists) import os print(os.path.exists) print(exists) print(os.path.__dict__['exists']) print(getattr(os.path,'exists')) #以上方法获得同一个对象
找到from子句中指定的模块加载并初始化它(注意不是导入)
对于import子句后的名称
先查from子句导入的模块是否具有该名称的属性
如果没有,则尝试导入该模块是否具有该名称的属性
还没有找到,就抛出ImportError异常
这个名称保存到本地名词空间,如果有as子句,则使用as子句后的名称
导入的模块不会消亡,只是没有标识符记得
导入的名词对象在创建后不会改变,只创建一次,重复导入或者重命名后,也不会新建对象
自定义模块
.py就是模块天然的模块
命名规则
模块就是文件名
必须符合标识符要求,test-module.py 不能作为文件名
不要使用系统模块名来命名,除非明确知道模块的用途
通常模块名未全小写,下划线来分割
模块搜素顺序
sys.modules 显示当前模块内已经导入的模块
sys.path 查看搜索顺序,python模块的路径搜索顺序
import sys for p in sys.path: print(p)
当加载模块时,需要从这个搜索路径中从前到后依次查找,并不会搜索这些目录的子目录
搜索到模块就加载,搜索不到就抛异常
路径也可以是字典,zip文件,egg文件。
.egg文件,由setuptools库创建的包,第三方库常用的格式,添加了元数据(版本号,依赖信息等)信息的zip文件
路径顺序为
程序主目录,程序运行的主程序的脚本所在的目录。
PYTHONPATH目录,环境变量PYTHONPATH设置的目录也是搜索模块的路径。
标准库目录,Python自带的库模块所在的目录
模块不会重复导入
模块不会重复导入,sys.modules储存所有已经加载过的所有模块,是一个字典
模块运行
__name__ 每个模块都有保存模块名,每个模块都有一个__name__特殊变量来储存当前模块名,如果不指定,则默认为源代码文件名,如果是包则有限定名
解释器初始化时,会初始化sys.module字典(保存已加载的模块),加载builtins(全局函数,常量)模块、__main__模块、sys模块,以及初始化模块搜索路径sys.path
当从标准输入(命令行方式输入),脚本(*.py),或交互式读取的时候,会将模块的__name__设置为__main__,模块的顶层代码就在__main__这个作用域中执行。顶层代码:模块中缩进最外层的代码
如果是import导入,则__name__默认就是模块名
脚本语言,可以直接运行,但有列外
if __name__ =='__main__' : 用途
本模块的功能测试,对于非本模块不会执行这个语句
避免主模块变更的副作用
顶层代码没有封装,主模块使用正常,但是一旦变更主模块,原来的变成了导入模块,由于原来代码没有封装,就会一起执行。
可以在该语句下放例子,测试代码
模块属性
__file__ 字符串,源文件路径
__cached__ 字符串,编译后的字节码文件路径
__spec__ 显示模块的规范
__name__ 模块名
__package__ 当模块是包,同__name__;否则,可以设置为顶级模块的空字符串
包
目录可以当作包来看,包是特殊的模块。
目录不可以写入数据,__init__,包的特殊文件,初始化目录,目录的代码就在这个文件中
目录含有__init__就叫做包 package
子模块
包目录下的py文件,子目录都是其子模块
包可以没有__init__.py 不会影响导入,但是最好少做,尽量保留该文件
包可以更好的组织模块,尤其是大模块,可以拆分成很多子模块,便于使用某些功能就加载相应的模块
包目录中的__init__.py 是包第一次导入时执行的,内容可以是空,也可以是用于该包初始化工作的代码,最好不要删除它(低版本不可删除)
包目录间只能使用.作为分隔符,表示模块及子模块的层级关系
模块就是命名空间,其内部的顶层标识符,都是它的属性,可以通过__dict__或dir(module)查看
包也是模块,但模块不一定是包,包是特殊的模块,是一种组织方式,它包含__path__属性
dir只能到模块的边界,就是模块的界限
模块的边界非常强,模块下的文件都受模块管理,相当于国界
作用域,globals,模块是最大范围,locals显示当前作用域
locals() 和dir()不带参数时意义相同
绝对导入、相对导入
绝对导入,在import语句或者from导入模块,模块名称最前面不是以.点开头的
绝对导入总是去模块搜索路径中找,会先看是否已经加载该模块
相对导入,只能在包内使用,且只能在from后,import语法不支持
使用.表示当前目录内,
..表示上一级
使用了相对导入的模块不能作为主模块运行
一般用在包内部,相对导入只是为了内部相互引用资源的,不是为了直接运行,对于包来说,正确的使用方式是在顶级模块中使用这些包
访问控制
像_或者__开头的文件,都可以导入,模块内没有访问控制,所有变量都不做特殊处理
隐藏名对模块无用
私有属性在类下有用,在模块下无用
from ... import * 和 __all__
使用*时会将公共模块放入名词空间,但是如果在模块内定义了__all__,就可以控制给*访问的模块
__all__时一个列表,元素是字符串,每一个元素都是一个模块内的变量名
__all__只影响*号导入
总结:
使用from xyz import * 导入
1、如果模块没有__all__,只导入没有下划线开头的该模块下的变量。如果时包,子模块也不会导入,除非在__all__中设置,或__init__.py中导入
2、如果模块有__all__,只导入__all__列表中指定的名称,哪怕这个名词是下划线开头的或是子模块
3、from...import * 导入简单,但是会大量带入不使用的变量,甚至有可能造成名称的冲突,而__all__可以控制被导入模块在这种导入方式下提供想提供的变量名称。
编写时,应该尽量加入__all__
使用from module import name1,name2 导入
这种方式导入是明确的,哪怕是导入子模块,或者导入下划线开头的名字
模块对象是同一个,模块变量也是同一个,对模块变量的修改会影响所有使用者,
除非万不得已,或者明确知道自己在做什么,否则不要轻易修改模块的变量
打补丁,也可以修改模块的变量,类,函数等内容