模块
1 什么是模块
模块就是一系列功能的集合体,分为三大类
- I:内置的模块
- II:第三方的模块
- III:自定义的模块
一个python文件本身就一个模块,文件名m.py,模块名叫m
ps:模块有四种形式
1 使用python编写的.py文件
2 已被编译为共享库或DLL的C或C++扩展
3 把一系列模块组织到一起的文件夹(注:文件夹下有一个__init__.py文件,该文件夹称之为包)
4 使用C编写并链接到python解释器的内置模块
2 为何要用模块
- 内置与第三的模块拿来就用,无需定义,这种拿来主义,可以极大地提升自己的开发效率
- 自定义的模块
可以将程序的各部分功能提取出来放到一模块中为大家共享使用
好处是减少了代码冗余,程序组织结构更加清晰
3 如何用模块
1 import语句
#文件名:foo.py
x=1
def get():
print(x)
def change():
global x
x=0
class Foo:
def func(self):
print('from the func')
#其他文件
import foo #导入模块foo
a=foo.x #引用模块foo中变量x的值赋值给当前名称空间中的名字a
foo.get() #调用模块foo的get函数
foo.change() #调用模块foo中的change函数
obj=foo.Foo() #使用模块foo的类Foo来实例化,进一步可以执行obj.func()
要想在另外一个py文件中引用foo.py中的功能,需要使用import foo,
首次导入模块会做三件事:
1、执行源文件代码foo.py
2、产生一个foo.py的名称空间用于存放源文件执行过程中产生的名字
3、在当前执行文件所在的名称空间中得到一个名字foo,该名字指向新创建的模块名称空间,若要引用模块名称空间中的名字,需要加上该前缀
注意:之后的导入,都是直接引用首次导入产生的foo.py名称空间,不会重复执行代码
2 引用
print(foo.x)
print(foo.get)
print(foo.change)
#强调1:模块名.名字,是指名道姓地问某一个模块要名字对应的值,不会与当前名称空间中的名字发生冲突
x=1111111111111
print(x)
print(foo.x)
#强调2:无论是查看还是修改操作的都是模块本身,与调用位置无关
import foo
x=3333333333
# foo.get()
foo.change()
print(x)
print(foo.x)
foo.get()
3 导入模块规范
可以以逗号为分隔符在一行导入多个模块,也可以在函数内导入模块
# 建议如下所示导入多个模块
# import time
# import foo
# import m
# 不建议在一行同时导入多个模块
import time,foo,m
导入的顺序
-
I. python内置模块
-
II. 第三方模块
-
III. 程序员自定义模块
自定义模块的命名应该采用纯小写+下划线的风格
import time
import sys
import 第三方1
import 第三方2
import 自定义模块1
import 自定义模块2
import 自定义模块3
4 模块可以取别名 as
import foo as f # f=foo
f.get()
import abcdefgadfadfas as mmm
mmm.f1
5 模块是第一类对象
即可以对导入的模块进行赋值,可以当做参数传递,可以做返回值,可以当做容器的元素等
4. 模块的导入
1 一个python文件有两种用途
- 被当成程序运行
- 被当做模块导入
2 二者的区别是什么?
以文件foo为例:
1、当foo.py被运行时,__name__的值为'__main__'
2、当foo.py被当做模块导入时,__name__的值为'foo'
print('模块foo==>')
# __all__=['x',] # 控制*代表的名字有哪些
x=1
def get():
print(x)
def change():
global x
x=0
def say():
print('我还活在内存中呢。。。。')
if __name__ == '__main__':
print('文件被执行')
get()
change()
else:
# 被当做模块导入时做的事情
print('文件被导入')
pass
对于import导入的模块来说
3 import导入方式的优缺点
impot导入模块在使用时必须加前缀"模块."
优点:肯定不会与当前名称空间中的名字冲突
缺点:加前缀显得麻烦
4 from...impot...语句
导入中的三个过程
- 产一个模块的名称空间
- 运行foo.py将运行过程中产生的名字都丢到模块的名称空间去
- 在当前名称空间拿到一个名字,该名字与模块名称空间中的某一个内存地址
from foo import x # x=模块foo中值的内存地址
from foo import get
from foo import change
print(x)
print(get)
print(change)
x=333333333
print(x)
get()
change()
get()
print(x)
from foo import x # x=新地址
print(x)
对于from导入的模块来说
5 from...impot...的优缺点
from...impot...导入模块在使用时不用加前缀
优点:代码更精简
缺点:容易与当前名称空间混淆
from foo import x # x=模块foo中值1的内存地址
x=1111
from…import的注意事项
- 一行导入多个名字(不推荐)
- 一次性导入模块中的所有名字可以使用: *
- 可以对导入的模块起别名
from foo import x,get,change
from foo import get as g
name='egon'
from foo import *
print(name)
from socket import *
'''
了解知识: * 导入时是访问对应模块的__all__的值,是一个列表
__all__=['x',] # 控制*代表的名字有哪些
'''
6 模块的搜索路径优先级
无论是import还是from...import在导入模块时都涉及到查找问题
优先级:
1、内存(内置模块)
2、硬盘:按照sys.path中存放的文件的顺序依次查找要导入的模块
# 值为一个列表,存放了一系列的对文件夹
# 其中第一个文件夹是当前执行文件所在的文件夹
>>> import sys
>>> sys.path
['', 'E:\Python\Python38\python38.zip', 'E:\Python\Python38\DLLs', 'E:\Python\Python38\lib', 'E:\Python\Python38', 'E:\Python\Python38\lib\site-packages']
>>
sys.path中的第一个路径通常为空,代表执行文件所在的路径,所以在被导入模块与执行文件在同一目录下时肯定是可以正常导入的,而针对被导入的模块与执行文件在不同路径下的情况,为了确保模块对应的源文件仍可以被找到,需要将源文件foo.py所在的路径添加到sys.path中,假设foo.py所在的路径为/pythoner/projects/
import sys
sys.path.append(r'/pythoner/projects/') #临时添加
#找foo.py就把foo.py的文件夹添加到环境变量中,获取文件的绝对路径,再添加
import foo #无论foo.py在何处,我们都可以导入它了
了解:sys.modules查看已经加载到内存中的模块
import sys
import foo # foo=模块的内存地址
del foo
def func():
import foo # foo=模块的内存地址
func()
# print('foo' in sys.modules)
print(sys.modules)
7 编写一个规范的模块
#!/usr/bin/env python #通常只在类lunix环境有效,作用是可以使用脚本名来执行,而无需直接调用解释器。
"The module is used to..." #模块的文档描述
import sys #导入模块
x=1 #定义全局变量,如果非必须,则最好使用局部变量,这样可以提高代码的易维护性,并且可以节省内存提高性能
class Foo: #定义类,并写好类的注释
'Class Foo is used to...'
pass
def test(): #定义函数,并写好函数的注释
'Function test is used to…'
pass
if __name__ == '__main__': #主程序
test() #在被当做脚本执行时,执行此处的代码
包
1 什么是包
包就是一个包含有__init__.py文件的文件夹
2 为何要有包
包的本质是模块的模块的一种形式,包是用来被当做模块导入
3 怎么使用包
import mmm
print(mmm.x)
print(mmm.y)
mmm.say()
from mmm import x
导入包的三个步骤
1、产生一个名称空间
2、运行包下的_init_.py文件,将运行过程中产生的名字都丢到1的名称空间中
3、在当前执行文件的名称空间中拿到一个名字mmm,mmm指向1的名称空间
强调:
1.关于包相关的导入语句也分为import和from ... import ...两种在导入时都必须遵循一个原则:
凡是在导入时带点的,点的左边都必须是一个包,否则非法。
2、包A和包B下有同名模块也不会冲突,如A.a与B.a来自俩个命名空间
3、import导入文件时,产生名称空间中的名字来源于文件,import 包,产生的名称空间的名字同样来源于文件,即包下的_init_.py,导入包本质就是在导入该文件
4 绝对导入与相对导入
# 绝对导入,以包的文件夹作为起始来进行导入
#pool下的__init__.py
from pool import versions
# 相对导入:仅限于包内使用,不能跨出包(包内模块之间的的导入,推荐使用相对导入)
# .:表示当前文件夹
# ..:表示上一层文件夹
# 局限性:.不能超出foo之外
#pool下的__init__.py
from . import versions
'''
强调:
1、相对导入不能跨出包,所以相对导入仅限于包内模块之间相互导入
2、绝对导入是没有任何限制的,所以绝对导入是一种通用的导入方式
'''
#针对包内部模块之间的相互导入推荐使用相对导入,需要特别强调:
'''
1、相对导入只能在包内部使用,用相对导入不同目录下的模块是非法的
2、无论是import还是from-import,但凡是在导入时带点的,点的左边必须是包,否则语法错误
'''
5 from 包 import *
'''
在使用包时同样支持from pool.futures import * ,
毫无疑问*代表的是futures下__init__.py中所有的名字,通用是用变量__all__来控制*代表的意思
'''
#futures下的__init__.py
__all__=['process','thread']
6 使用包的意义
'''
包内部的目录结构通常是包的开发者为了方便自己管理和维护代码而创建的,
这种目录结构对包的使用者往往是无用的,此时通过操作__init__.py可以“隐藏”包内部的目录结构,
降低使用难度,比如想要让使用者直接使用
'''
import pool
pool.check()
pool.ProcessPoolExecutor(3)
pool.ThreadPoolExecutor(3)
# 需要操作pool下的__init__.py
from .versions import check
from .futures.process import ProcessPoolExecutor
from .futures.thread import ThreadPoolExecutor
软件开发的目录规范
为了提高程序的可读性与可维护性,我们应该为软件设计良好的目录结构,这与规范的编码风格同等重要。软件的目录规范并无硬性标准,只要清晰可读即可,假设你的软件名为foo,推荐目录结构如下
1 软件目录结构
Foo/
|-- core/ # 存放业务逻辑相关代码
| |-- core.py
|
|-- api/ # 存放接口文件,接口主要用于为业务逻辑提供数据操作。
| |-- api.py
|
|-- db/ # 放操作数据库相关文件,主要用于与数据库交互
| |-- db_handle.py
|
|-- lib/ # 存放程序中常用的自定义模块
| |-- common.py
|
|-- conf/ # 存放配置文件
| |-- settings.py
|
|-- run.py # 程序的启动文件,一般放在项目的根目录下,因为在运行时会默认将运行文件所在的文件夹 sys.path的第一个路径,这样就省去了处理环境变量的步骤
|-- setup.py # 安装、部署、打包的脚本。
|-- requirements.txt # 存放软件依赖的外部Python包列表
|-- README # 项目说明文件
2 软件说明README
关于README的内容,这个应该是每个项目都应该有的一个文件,目的是能简要描述该项目的信息,让读者快速了解这个项目。它需要说明以下几个事项:
'''
1、软件定位,软件的基本功能;
2、运行代码的方法: 安装环境、启动命令等;
3、简要的使用说明;
4、代码目录结构说明,更详细点可以说明软件的基本原理;
5、常见问题说明。
'''