模块 软件目录规范
1 模块
1.1 python文件的用途
- 将文件当作程序运行;
- 将文件当作模块,导入到其它文件中。
区别:
文件当作程序运行时,由于没有其它文件引用这个文件,因此运行结束后其名称空间会被销毁;
文件当作模块导入到其它文件时,会先运行模块文件,产生模块文件的名称空间,并在主文件中存在对模块名称空间的引用。因此即使模块文件运行结束,其名称空间也不会销毁,而是等待主文件运行结束后不存在其它文件对这个模块文件的引用时,模块文件的名称空间才会销毁。
1.2__name__属性
如果需要一些代码仅仅在文件运行时执行,在导入到其它文件时不执行,可以使用如下判断语句。
if __name__ == '__main__':
pass
当运行文件时,这个文件的__name__的值是__main__,
当文件作为模块被导入到其它文件中时,这个文件的__name__的值是模块名。
1.3 from…import…
1.3.1 import… 优缺点
使用模块中的变量或函数时必须加前缀“模块.”
优点:不会与当前名称空间中的名字冲突。
缺点:每次使用时必须加前缀,麻烦。
1.3.1 from…import…
main.py
from module import func
导入的函数func的名字属于主文件main的名称空间,
导入的函数func的名字指向的是模块module中对应的内存地址。
使用from…import…语句导入模块时
- 执行 module.py;
- 产生 module.py 的名称空间,将 module.py 执行过程中产生的名字存放到此名称空间中;
- 在主文件中产生一个名字,指向2中模块的名称空间中对应的内存地址。
起别名
from...import...as 别名
注意,对于函数,找名字永远要在函数的定义阶段去寻找,与在哪里调用无关。
module.py
x = 1
def set_x(input_x):
global x
x = input_x
def get_x():
print(f'模块中的x值为{x}')
return x
main.py
from a import x
from a import set_x
from a import get_x
res1 = get_x() # 模块中的x值为1
print(res1) # 1
print(f'主文件中的x值为{x}') # 主文件中的x值为1
x = 2 # 将主文件中的x指向的值变为2,不影响模块中的定义。
res1 = get_x() # 模块中的x值为1
print(res1) # 1
print(f'主文件中的x值为{x}') # 主文件中的x值为2
set_x(3)
res2 = get_x() # 模块中的x值为3
print(res2) # 3
print(f'主文件中的x值为{x}') # 主文件中的x值为2
def func(x: int, y: tuple) -> str:
print(x, y)
优点:使用时不需要加前缀,代码精简。
缺点:容易与当前名称空间的名字发生混淆。
1.3.2__all__属性
__all__以列表的形式指定了一个模块中哪些函数,类,全局变量等允许被导入到其它文件中。
1.4 循环导入问题
文件作为模块被导入时会开辟名称空间并执行其代码,如果导入后继续导入,则无需进行这些操作,直接使用已存在的名称空间。
由于模块被重复导入时不会产生新的名称空间,因此产生了循环导入问题。
main.py
import m1
m1.py
from m2 import mm2
# 第一次导入m1的过程中,在上一行去导入m2了,未将变量mm1的名字放入m1的名称空间中。
mm1 = 1
m2.py
# 第二次导入m1,使用之前存在的m1名称空间,但该名称空间不存在mm1,因此这里报错。
from m1 import mm1
mm2 = 1
解决方法:
- 互相导入说明文件中存在共享的对象,应该将它们进一步存放在一个公用的模块中,让这些文件去调用这个模块;
- 将可能被互相导入的对象定义语句放在import语句前面;
- 如果仅需要为某个函数导入其它模块的对象,可以将import语句放入函数体内,利用定义函数时不会执行函数体代码的特点,来避免循环导入问题。
1.5 模块搜索路径的优先级
会从以下位置查找模块
- 内存
内置模块和已导入的模块
可以通过sys.modules以字典的形式查看已经加载到内存中的模块。 - 硬盘,按照sys.path存放的路径依次查找。
import sys
print(sys.path)
sys.path以列表的方式存储了待搜索的路径。
其中第一个路径是执行文件所在的文件夹。
1.6 针对导入模块的优化机制
一个模块一般会占据很大的内存空间,为了减少重复导入模块产生的开销,解释器做了一些优化,模块只需导入一次,在文件结束运行前已导入的模块不会被销毁。
import mod1 # 自定义模块
del mod1
print(sys.modules) # mod1存在
通过命令del解除了mod与模块内存地址的绑定关系,内存中的模块依旧不会被销毁。
def func():
import mod2
func()
print(sys.modules) # mod2存在
函数执行结束后其局部名称空间会被销毁,但导入的模块不会,依旧存在于内存中。
1.7 编写模块的规范
- 在模块前添加对模块的描述信息;
- 导入其它模块;
- 定义全局变量,尽量使用局部变量;
- 定义类;
- 定义函数;
- 当此模块需要作为脚本文件运行时,使用
if __name__ == '__main__':
2 补充 函数的类型提示
python语言属于 解释型,强类型,动态型 语言。
其中强类型指的是变量一旦定义,其类型由值确定,不会在变量调用过程中被轻易改变,除非进行强制类型转换或者重新定义变量。
其中动态型指的是变量在赋值时才确定其类型,不需要事先为变量指定类型,即动态获取。
Type hinting
在定义函数时提供变量和返回值的类型提示信息。
参数默认值写在提示信息后
def func(a: int, b: tuple, c: dict={'a': 96}) -> list:
return [a, b, c]
print(func.__annotations__)
# {'a': <class 'int'>, 'b': <class 'tuple'>, 'c': <class 'dict'>, 'return': <class 'list'>}
3 包
3.1 什么是包
包是一个包含有__init__.py的文件夹。
导入包时运行的是包中的__init__.py,产生的名称空间来自于__init__.py。
- 在 python3 中,即使包内没有__init__.py,导入(import)这个包时也不会报错,而在 python2 中,包内必须有该__init__.py,否则导入时报错;
- 包以及包内的文件不是为了直接运行,而是为了在被导入后被其它文件使用,包的本质是模块。
3.2 为什么需要包?
包的本质就是模块的一种形式,是用来当作模块被导入的。
作为模块的设计者,面对越来越多的功能,需要将各个功能分门别类地放入各个文件夹下的文件中,以便更好地管理包,使组织结构更加清晰。
3.3 如何使用包
将需要公开的分散到包内各个文件中的全局变量,函数,类等导入到__init__.py中。
环境变量是以执行文件为基准的,即所有被导入的模块以及其它文件引用的sys.path都是参照执行文件的sys.path。
3.3.1 导入方式
导入原则:
限制:导入语句(import …/ from … import …)中凡是带点的,点的左边必须是一个包名,否则非法。允许多个包的嵌套,例如 import 顶级包.子包.子模块。
在导入包后使用包提供的功能时,没有这种限制,点的左边可以是包,模块,函数,类(可以用点的方式调用属性)。
导入方式分类:
- 绝对导入
以顶级包的文件夹作为顶级目录。
绝对导入参照的是sys.path。
import 顶级包.子包.子模块
- 相对导入
. 代表当前文件夹
. . 代表上一层文件夹,以此类推。
但是向外逐层跳出文件夹时不能跳出顶级包之外。相对导入仅能在包内模块之间互相导入,不能跨出包。
包内模块之间互相导入推荐使用 相对导入。
相对导入参照的是当前文件所在的位置。
4 软件开发的目录规范
4.1 命名风格
项目名:驼峰命名法
项目内文件名及文件夹名:纯小写字母+下划线
4.2 目录结构
文件夹意义
- bin
用于存放可执行文件,包括启动文件start.py; - conf
用于存放项目配置文件,可以供使用者进行修改; - core
用于存放核心业务逻辑代码文件(用户视图层); - db
用于存放数据文件及操作数据的代码文件(数据处理层); - interface
用于存放接口文件(逻辑接口层); - lib
用于存放共享库/共享文件,即保存公共功能的文件/包等; - log
用于存放日志文件;
项目名/
|-- bin/
| |-- start.py # 启动文件
|
|-- conf/
| |-- settings.py # 配置文件,例如文件路径等。
|
|-- core/
| |-- core.py
|
|-- db/
| |-- db_file.txt
| |-- db_handler.py
|
|-- lib/
| |-- common.py # 存放公共功能的代码,例如装饰器等。
|
|-- log/
| |-- log.txt
|
|-- run.py # 启动文件
|-- setup.py
|-- requirements.txt
|-- README.md
文件意义:
run.py:程序的启动文件,一般放在项目的根目录下,因为在程序运行时会默认将运行文件所在的根文件夹作为sys.path的第一个路径,这样可以省去处理环境变量等操作。(下面的例子中使用bin/start.py作为启动文件)
setup.py:安装、部署、打包的脚本。
requirements.txt:存放项目所依赖的外部包的列表。
README.md:项目说明文件。
项目内文件的相互导入使用绝对导入,参照的是sys.path,而sys.path参照的是执行文件/启动文件start.py。
属性__file__ 表示的是当前文件的绝对路径。
os.path.dirname(filepath) 返回的是输入路径的所在的文件夹路径。
start.py
import os
import sys
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
sys.path.append(BASE_DIR) # 将项目所在的根目录加入到环境变量中
from core import core
if __name__ == '__main__':
core.run()