1、简介
一般来说,编程语言中,库、包、模块是同一种概念,是代码组织方式。
Python中只有一种模块对象类型,但是为了模块化组织模块的便利,提供了"包"的概念。
模块module:指的是Python的源代码文件。
包package:指的是模块组织在一起的和包名同名的目录及其相关文件。
2、导入语句
2.2、import语句
2.2.1、语法介绍
语句 含义
import # 模块1[,模块2,...] 完全导入
import ... as ... # ///模块别名
2.2.2、简介
1、 找到指定的模块,加载和初始化它,生成模块对象。找不到,抛出异常
2、在import所在的作用域的局部命名空间中,增加名称和上一步创建的对象关联
2.2.3、示例
# import ...
import functools # 导入模块
print(dir()) # [..., 'functools']
print(functools, type(functools)) # <module 'functools' from 'path/to/functools.py'> <class 'module'>
print(functools.wraps) # <function wraps at 0x00000000010FB400>
import os.path # 导入os.path,os加入当前名词空间
print(dir()) # [..., 'os']
print(os, type(os)) # <module 'os' from 'path/to/os.py'> <class 'module'>
print(os.path) # 完全限定名称访问path
# import ... as
import os.path as osp # 导入os.path并赋给osp
print(dir()) #[..., 'osp']
print(osp) # <module 'ntpath' from 'path/to/path.py'>
def testimport():
import os.path # 局部
print(dir())
testimport()
print(globals().keys())
import os.stat # 报错:ModuleNotFoundError: No module named 'os.stat'; 'os' is not a package
2.2.4、总结
导入顶级模块,其名称会加入到本地名词空间中,并绑定到其模块对象
导入非顶级模块,只将其顶级模块名称加入到本地名词空间中。导入的模块必须使用完全限定名称来访问
如果使用了as,as后的名称直接绑定到导入的模块对象,并将该名称加入到本地名词空间中
import 之后只能是模块类型
2.3、from语句
2.3.1、语法介绍
语句 # 含义
from ... import ... # 部分导入
from ... import ... as ... # 别名
2.3.2、示例
from pathlib import Path, PosixPath # 在当前名词空间导入该模块指定的成员
print(dir()) # [..., 'Path', 'PosixPath']
from pathlib import * # 在当前名词空间导入该模块所有公共成员(非下划线开头成员)或指定成员
print(dir()) # [..., 'Path', 'PosixPath', 'PurePath', 'PurePosixPath', 'PureWindowsPath', 'WindowsPath']
from functools import wraps as wr, partial # 别名
print(dir()) # [..., 'wr', 'partial']
from os.path import exists # 加载、初始化os、os.path模块,exists加入本地名词空间并绑定
if exists('c:/t'):
print('Found')
else:
print('Not Found')
print(dir())
print(exists)
# 4种方式获得同一个对象exists
import os
print(os.path.exists)
print(exists)
print(os.path.__dict__['exists']) # 字符串
print(getattr(os.path, 'exists')) # 字符串
2.3.3、总结
找到from子句中指定的模块,加载并初始化它(注意不是导入)
对于import子句后的名称
1、先查from子句导入的模块是否具有该名称的属性
2、如果不是,则尝试导入该名称的子模块
3、还没有找到,则抛出ImportError异常
4、这个名称保存到本地名词空间中,如果有as子句,则使用as子句后的名称
from pathlib import Path # 导入类Path
print(Path, id(Path))
import pathlib as pl # 导入模块使用别名
print(dir())
print(pl)
print(pl.Path, id(pl.Path))
# 可以看出导入的名词Path和pl.Path是同一个对象
3、自定义模块
3.1、简介
3.2、示例
# test1.py文件
print('This is test1 module')
class A:
def showmodule(self):
print(1, self.__module__, self)
print(2, __class__, id(__class__))
a = A()
a.showmodule()
# test2.py文件
import test1
a = test1.A()
a.showmodule()
# test3.py文件
from test1 import A as cls
a = cls()
a.showmodule()
3.3、自定义模块命名规范
1、模块名就是文件名
2、模块名必须符合标识符的要求,是非数字开头的字母、数字和下划线的组合。test-module.py这样的文件名不能作为模块名。也不要使用中文。
3、不要使用系统模块名来避免冲突,除非你明确知道这个模块名的用途
4、通常模块名为全小写,下划线来分割
4、模块特征
4.1、模块搜索顺序
4.1.1、示例:使用 sys.path 查看搜索顺序
import sys
print(*sys.path, sep='\n')
显示结果为,python模块的路径搜索顺序
当加载一个模块的时候,需要从这些搜索路径中从前到后依次查找,并不搜索这些目录的子目录。
搜索到模块就加载,搜索不到就抛异常
路径也可以为字典、zip文件、egg文件。
.egg文件,由setuptools库创建的包,第三方库常用的格式。添加了元数据(版本号、依赖项等)信息的zip文件
4.1.2、路径顺序
1、程序主目录,程序运行的主程序脚本所在的目录
2、PYTHONPATH目录,环境变量PYTHONPATH设置的目录也是搜索模块的路径
3、标准库目录,Python自带的库模块所在目录
sys.path可以被修改,增加新的目录
4.2、模块的重复导入
4.2.1、示例
# test1.py文件
print('This is test1 module')
class A:
def showmodule(self):
print(1, self.__module__, self)
print(2, __class__, id(__class__))
a = A()
a.showmodule()
# test2.py文件
import test1
print('local module')
import test1
import test1
4.2.2、分析
从执行结果来看,不会产生重复导入的现象。
所有加载的模块都会记录在sys.modules中,sys.modules是存储已经加载过的所有模块的字典。
打印sys.modules可以看到builtins、os、os.path、sys等模块都已经加载了。
4.3、模块运行
4.3.1、简介
__name__ ,每个模块都会定义一个 __name__ 特殊变量来存储当前模块的名称,如果不指定,则默认为源代码文件名,如果是包则有限定名。
解释器初始化的时候,会初始化sys.modules字典(保存已加载的模块),加载builtins(全局函数、常
量)模块、 __main__ 模块、sys模块,以及初始化模块搜索路径sys.path
Python是脚本语言,任何一个脚本都可以直接执行,也可以作为模块被导入。
当从标准输入(命令行方式敲代码)、脚本($ python test.py)或交互式读取的时候,会将模块的
__name__ 设置为 __main__ ,模块的顶层代码就在 __main__ 这个作用域中执行。顶层代码:模块中缩进最外层的代码。
如果是import导入的,其 __name__ 默认就是模块名
4.3.2、示例
# test1.py文件
import test2
# test2.py文件
# 判断模块是否以程序的方式运行 $python test.py
if __name__ == '__main__':
print('in __main__') # 程序的方式运行的代码
else:
print('in imported module') # 模块导入的方式运行的代码
4.3.3、if __name__ == '__main__': 用途
1、本模块的功能测试
对于非主模块,测试本模块内的函数、类
2、避免主模块变更的副作用
顶层代码,没有封装,主模块使用时没有问题。但是,一旦有了新的主模块,老的主模块成了被导入模块,由于原来代码没有封装,一并执行了。
4.4、模块的属性
4.4.1、属性介绍
属性 含义
__file__ # 字符串,源文件路径
__cached__ # 字符串,编译后的字节码文件路径
__spec__ # 显示模块的规范
__name__ # 模块名
__package__ # 当模块是包,同 __name__ ;否则,可以设置为顶级模块的空字符串
4.4.2、示例
import os as t3
for k, v in t3.__dict__.items():
print(k, str(v)[:80])
print(dir(t3))
for name in dir(t3):
print(getattr(t3, name))
5、包
5.1、简介
5.2、Python模块支持目录吗?
Python中,目录可以作为模块,这就是包,不过代码需要写在该目录下 __init__.py 中。包的 __file__ 就指向 __init__.py 这个文件。
5.3、子模块
5.3.1、简介
m
|-- __init__.py
|-- m1.py
|-- m2
|-- __init__.py
|-- m21
|-- __init__.py
|-- m22.py
5.3.2、示例
# 注意观察已经加载的模块、当前名词空间的名词
# import m
# import m.m1
# import m.m2.m21
# from m import m1
from m.m2 import m21
print('-' * 30)
print(*filter(lambda x: x.startswith('m'), dir()))
print('-' * 30)
import sys
print(sorted(filter(lambda x: x.startswith('m'), sys.modules.keys())))
# ------------------------------
# m21
# ------------------------------
# ['m', 'm.m2', 'm.m2.m21', 'marshal']
删除 __init__.py 试一试,可以发现删除并不影响导入,但是这不是良好的习惯,请保留__init__.py 文件
5.4、模块和包的总结
包能够更好的组织模块,尤其是大的模块,其代码行数很多,可以把它拆分成很多子模块,便于使用某些功能就加载相应的子模块。
包目录中 __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。
6、绝对导入、相对导入
6.1、绝对导入
在import语句或者from导入模块,模块名称最前面不是以.点开头的
绝对导入总是去模块搜索路径中找,当然会查看一下该模块是否已经加载
6.2、相对导入
只能用在from语句中
使用.点号,表示当前目录内
.. 两点表示上一级目录
... 三点表示上上一级
只在包内使用,一般不要在顶层模块中使用相对导入
一旦一个模块中使用相对导入,就不可以作为主模块运行了
# 举例a.b.c模块,a、b是目录,c是模块c.py
# c的代码如下
from . import d # imports a.b.d
from .. import e # imports a.e
from .d import x # a.b.d.x
from ..e import x # a.e.x
7、访问控制
7.1、下划线开头的模块名
_ 或者 __ 开头的模块是否能够被导入呢?
创建文件名为 _xyz.py 或者 __xyz.py 测试。
都可以成功的导入,因为它们都是合法的标识符,就可以用作模块名。
7.2、模块内的标识符
# xyz.py
print(__name__)
A = 5
_B = 6
__C = 7
__my__ = 8
# test.py中
import xyz
import sys
print(sorted(sys.modules.keys()))
print(dir())
print(xyz.A, xyz._B, xyz.__C, xyz.__my__)
普通变量、保护变量、私有变量、特殊变量,都没有被隐藏,也就是说模块内没有私有的变量,在模块中定义不做特殊处理。
# test.py中
# from语句导入
from xyz import A, _B as B, __my__, __C as C
import sys
print(sorted(sys.modules.keys()))
print(dir())
print(A, B, __my__, C)
7.3、from ... import * 和 __all__
7.3.1、使用from ... import * 导入
# xyz.py
print(__name__)
A = 5
_B = 6
__C = 7
__my__ = 8
# test.py中
from xyz import *
import sys
print(sorted(sys.modules.keys()))
print(dir())
print(locals()['A'])
A = 55
print(locals()['A']) # 思考这个A是谁的A了
7.3.2、使用 __all__
__all__ 是一个列表,元素是字符串,每一个元素都是一个模块内的变量名
# xyz.py中
__all__ = ["X", "Y"]
print(__name__)
A = 5
_B = 6
__C = 7
__my__ = 8
X = 10
Y = 20
# test.py中
from xyz import *
import sys
print(sorted(sys.modules.keys()))
print(dir())
#print(locals()['A']) # 报错
print(locals()['X'])
print(locals()['Y'])
7.3.3、修改 __all__ 列表,加入下划线开头变量,看看什么效果
# xyz.py中
__all__ = ["X", "Y", "_B", "__C"]
print(__name__)
A = 5
_B = 6
__C = 7
__my__ = 8
X = 10
Y = 20
# test.py中
from xyz import *
import sys
print(sorted(sys.modules.keys()))
print(dir())
#print(locals()['A'])
print(locals()['X'])
print(locals()['Y'])
print(locals()['_B'])
print(locals()['__C'])
可以看到使用from xyz import *导入 __all__ 列表中的名称
8、总结
8.1、使用 from xyz import * 导入
1、如果模块没有 __all__ , from xyz import * 只导入非下划线开头的该模块的变量。如果是包,子模块也不会导入,除非在 __all__ 中设置,或 __init__.py 中导入它们
2、如果模块有 __all__ , from xyz import * 只导入 __all__ 列表中指定的名称,哪怕这个名词是下划线开头的,或者是子模块
3、from xyz import * 方式导入,使用简单,但是其副作用是导入大量不需要使用的变量,甚至有可能造成名称的冲突。而 __all__ 可以控制被导入模块在这种导入方式下能够提供的变量名称,就
是为了阻止from xyz import *导入过多的模块变量,从而避免冲突。因此,编写模块时,应该尽量加入 __all__
8.2、from module import name1, name2 导入
这种方式的导入是明确的,哪怕是导入子模块,或者导入下划线开头的名称
程序员可以有控制的导入名称和其对应的对象