模块化
一般来说。编程语言中,库、包、模块是一个概念,是代码组织方式。
Python中只有一种模块对象类型,但是为了模块化组织模块便利,提供了包的概念。
模块module,指的是Python的源代码文件
包package,指的是模块组织在一起的和包名同名的目录及其相关文件。
导入语句
语句 | 含义 |
import 模块1[,模块2,……] | 完全导入 |
import……as…… | 模块别名 |
import语句
- 1、找到指定的模块,加载和初始化它,生成模块对象。找不到,抛出异常
- 2、在import所在的作用域的局部命名空间中,增加名称和上一步创建的对象关联
import functools
print(dir())
print(functools)
print(functools.wraps)
import os
print(dir())
import os.path
print(dir())
这里分别引用了os和os.path,但是全局属性都是只有os,表明无论引用如何,全局属性都只有最全的那一层,也就是模块那一层
import os.path
print(dir())
print(os)
print(os.path)
import os.path as osp
print(dir())
print(osp)
def fn():
import os.path
print(dir())
fn()
print(globals().keys())
总结:
导入顶级模块,其名称会加入到本地名词空间中,并绑定到其模块对象
导入非顶级模块,只将其顶级模块名称加入到本地名词空间中,导入的模块必须使用完全限定名称来访问。
如果使用了as,as后的名称直接绑定到导入的模块对象,并将该名称加入到本地名词空间中。
语句 | 含义 |
from……import…… | 部分导入 |
from……import……as…… | 别名 |
总结:
- 找到from子句中指定的模块,加载并初始化它
- 对于import子句后的名称
- 先查from子句导入的模块是否具有该名称的属性
- 如果不是,则尝试导入该名称的子模块
- 如果还没有,则抛出importError异常
- 这个名称保存在本地名词空间中,如果有as子句,则使用as子句后的名称。
from pathlib import Path
print(Path,id(Path))
import pathlib as p1
print(dir())
print(p1)
print(p1.Path,id(p1.Path))
自定义模块
自定义模块:.py文件就是一个模块
#base.py
print('This is test1.py')
class A:
def show(self):
print(1,self.__module__,self)
print(2,self.__dict__)
print(3,self.__class__.__dict__)
print(4,self.__class__.__name__)
a=A()
a.show()
#test2.py
import base
a=base.A()
a.show()
自定义模块命名规范
1、模块名就是文件名
2、模块名必须符合标识符的要求,是非数字开头的字母、数字和下划线的组合。
3、不要使用系统模块名来避免冲突,除非明确要使用模块名的用途
4、通常模块名全为小写,下划线来分割。
模块搜索顺序
使用sys.path查看搜索顺序
import sys
print(*sys.path,sep='\n')
显示结果为,Python模块的路径搜索顺序
当加载一个模块的时候,需要从这些搜索路径中从前到后依次查找,并不搜索这些路径的子目录。
搜索到模块就加载,搜索不到就抛异常。
路径也可以为字典、zip文件、egg文件。
.egg文件是由setuptools库创建的包,第三方库常用的格式,添加了元数据(版本号,依赖项等)信息的zip文件
路径顺序为:
- 1、程序主目录,程序运行的主程序脚本所在的目录
- 2、PYTHONPATH目录,环境变量PYTHONPATH设置的目录也是搜索模块的路径。
- 3、标准库目录,Python自带的库模块所在目录
模块的重复导入
#base.py
print('This is test1.py')
class A:
def show(self):
print(1,self.__module__,self)
print(2,self.__dict__)
print(3,self.__class__.__dict__)
print(4,self.__class__.__name__)
a=A()
a.show()
import base
print('~~~~~~~~~')
import base
import base
import base
从运行结果来看,不会出现重复导入的现象
所有加载的模块都会记录在sys.modules中,sys.modules是存储已经加载过的所有模块的字典。
打印sys.modules可以看到os、os.path都已经加载了。
模块运行
__name__每个模块都会定义一个__name__特殊变量来存储当前模块的名称,如果不指定,则默认为源代码文件名,如果是包则有限定名。
解释器初始化的时候,会初始化sys.modules字典(保存已加载的模块),加载builtins(全局函数、常量)模块、main__模块、sys模块、以及初始化模块搜索路径sys.path
Python是脚本语言,任何一个脚本都可以直接执行,也可以作为模块被导入。
当从标准输入(命令行方式敲代码)、脚本($Python test.py)或交互式读取的时候,会将模块的__name__设置为__main,模块的顶层代码就在__main__这个作用域中执行。顶层待嘛:模块中缩进最外层的代码。如果是import导入的,其__name__默认就是模块名
#test1.py文件
import test2
#test2.py文件
#判断模块是否以程序的方式运行$python test.py
if __name__=='__main__':
print('in __main__')
else:
print('in imported module')
if name==‘main’:的用途
1、本模式的功能测试,对于非主模块,测试本模块内的函数、类
2、避免主模块变更的副作用,顶层代码,没有封装,主模块使用时没有问题。但是,一旦有了新的主模块,老的主模块成了被导入模块,由于原来代码没有封装,一并执行了。
模块的属性
属性 | 含义 |
file | 字符串,源文件路径 |
cached | 字符串,编译后的字节码文件路径 |
spec | 显示模块的规范 |
name | 模块名 |
package | 当模块是包,同__name__,否则,可以设置为顶级模块的空字符串 |
包
Python模块支持模块
在项目中新建目录m,使用下面的代码
import m
print(m)
print(type(m))
print(dir(m))
pycharm中,创建Directory和创建python package不同,前者时创建普通的目录,后面是创建一个带有__init__.py文件的目录即包。
Python中,目录可以作为模块,这就是包,不过代码需要写在该目录下__init__.py中。
子模块
包目录下的py文件、子目录都是其子模块。
如上建立子模块目录和文件,所以的py文件中就写一句话
print(__name__)
#注意观察已经加载的模块,当前名词空间的名词
#import m
# import m.m1
# from m import m1
#from m.m2 import m21
import m.m2.m21
print(dir())
print('~~~~~~~')
import sys
print(sorted(filter(lambda x: x.startswith('m'),sys.modules.keys())))
删除__init__.py后发下并不影响导入,但建议保留。
模块和包的总结
包能够更好的组织模块,尤其是大的模块,其代码行数最多,可以把它分解成很多的子模块,便于使用某些功能就加载相应的子模块。
包目录中的__init__.py是在包第一次导入的时候就会执行,内容可以为空,也可以是用于该包初始化工作的代码。
最好不要删除__init__.py文件
导入子模块一定会加载父模块,但是导入父模块一定不会导入子模块。
包目录之间只能使用.点号作为间隔符,表示模块及其子模块的层级关系。
模块也是封装,如同类、函数,不过它能够封装变量、类、函数
模块就是命名空间,其内部的顶级标识符,都是它的属性,可以通过__dict__或者dir(module)查看。
包也是模块,但模块不一定是包,包是特殊的模块,它包含有__path__属性
问题
- from json import encoder之后,json.dump函数用不了?
- 原因是from json import encoder之后,当前名词空间没有Json,json模块已经加载过了,没有json的引用,无法使用dump函数。
- import json.encoder之后,json.dump函数是否能够使用?
- import json.encoder也加载json模块,但是当前名词空间有json,因此可以调用json.dump。
绝对导入、相对导入
- 绝对导入
在import语句或者from导入模块,模块名称最前面不是以.开头的
绝对导入总是去模块搜索中找,当前会查看一下该模块是否已经加载。 - 相对导入
只在包内使用,且只用在from语句中。
使用.点号,表示当前目录内
..表示上一级目录
不要在顶级模块中使用相对导入
访问控制
下划线开头的模块名
_或者__开头的模块都能够被导入
#xyz.py
print(__name__)
A=5
_B=6
__C=7
__my__=8
#other.py
import sys
import xyz
print(sorted(sys.modules.keys()))
print(dir())
print(xyz.A,xyz._B,xyz.__C,xyz.__my__)
可以看出普通变量、保护变量、私有变量、特殊变量都没有被隐藏,也就是说模块内没有私有的变量,在模块中定义不做特殊处理。
from xyz import A,_B,__C,__my__
import sys
print(sorted(sys.modules.keys()))
print(dir())
print(A,_B,__C,__my__)
from …import * 和__all__
先使用from…import*导入
#xyz.py
print(__name__)
A=5
_B=6
__C=7
__my__=8
#other.py
import sys
from xyz import *
print(sorted(sys.modules.keys()))
print(dir())
print(locals()['A'])
A=55
print(locals()['A'])
结果只导入了A,下滑线开头的都没有导入
使用__all__
__all__是一个列表,元素是字符串,每一个元素都是一个模块内的变量名
#xyz.py
print(__name__)
A=5
_B=6
__C=7
__my__=8
__all__=['A','_B']
#other.py
import sys
from xyz import *
print(sorted(sys.modules.keys()))
print(dir())
print(locals()['A'])
A=55
print(locals()['A'])
可以使用from xyz import *导入__all__列表中的名称
包和子模块
#__init__.py中
print(__name__)
x=1
#b.py中
print(__name__)
y=5
如何在另一个模块中访问到a.b.y
#方法一,直接导入b模块
import a.b
print(a.b.y)
#方法二,直接导入属性y
from a.b import y
print(y)
#方法三,from a import *
from a import *
print(b.y)
#方法四,在__init__.py中添加from . import b
from a import *
print(b.y)
init.py中有什么变量,则使用from m import *加载什么变量,这依然符合模块的访问控制。
总结
使用 from xyz import *导入
- 如果模块没有__all__,from xyz import *只导入非下划线开头的该模块的变量,如果是包,子模块也不会导入,除非__all__中设置,或__init__.py中导入它们
- 如果模块有__all__,from xyz import *只导入__all__列表中指定的名称,哪怕这个名词是下划线开头的,或者是子模块
- from xyz import * 方式导入,使用简单,但是其副作用是导入大量不需要使用的变量,甚至有可能造成名称的冲突。而__all__可以控制被导入模块在这种导入方式下能够提供的变量名称,就是为了阻止from xyz import *导入过多的模块变量,从而避免冲突。因此编写模块尽量加入__all__
模块变量的修改
import xyz
print(xyz.x)
xyz.x=100
print('~~~~~~~~~~~~~')
print(xyz.x)
模块对象是同一个,因此模块的变量也是同一个,对模块变量的修改,会影响所有使用者。
尽量不要修改模块的变量。