Python(八)—— 开发
18. 软件开发规范
软件开发,规范你的项目目录结构,代码规范,遵循PEP8规范等等,让你更加清晰合理开发
例如下面这个yuluoCSDN.py文件,是一个雨落CSDN网页的登录页面,包含登录、注册等多个功能,全部写到一个文件里,此时我们阅读起来就会觉得代码结构不清晰也不规范,运行起来效率也会非常低
status_dic = {
'username': None,
'status': False,
}
flag = True
def login():
i = 0
with open('register', encoding='utf-8') as f1:
dic = {i.strip().split('|')[0]: i.strip().split('|')[1] for i in f1}
while i < 3:
username = input('请输入用户名:').strip()
password = input('请输入密码:').strip()
if username in dic and dic[username] == password:
print('登录成功')
return True
else:
print('用户名密码错误,请重新登录')
i += 1
def register():
with open('register', encoding='utf-8') as f1:
dic = {i.strip().split('|')[0]: i.strip().split('|')[1] for i in f1}
while 1:
print('\033[1;45m 欢迎来到注册页面 \033[0m')
username = input('请输入用户名:').strip()
if not username.isalnum():
print('\033[1;31;0m 用户名有非法字符,请重新输入 \033[0m')
continue
if username in dic:
print('\033[1;31;0m 用户名已经存在,请重新输入 \033[0m')
continue
password = input('请输入密码:').strip()
if 6 <= len(password) <= 14:
with open('register', encoding='utf-8', mode='a') as f1:
f1.write(f'\n{username}|{password}')
status_dic['username'] = str(username)
status_dic['status'] = True
print('\033[1;32;0m 恭喜您,注册成功!已帮您成功登录~ \033[0m')
return True
else:
print('\033[1;31;0m 密码长度超出范围,请重新输入 \033[0m')
def auth(func):
def inner(*args, **kwargs):
if status_dic['status']:
ret = func(*args, **kwargs)
return ret
else:
print('\033[1;31;0m 请先进行登录 \033[0m')
if login():
ret = func(*args, **kwargs)
return ret
return inner
@auth
def article():
print(f'\033[1;32;0m 欢迎{status_dic["username"]}访问文章页面\033[0m')
@auth
def diary():
print(f'\033[1;32;0m 欢迎{status_dic["username"]}访问日记页面\033[0m')
@auth
def comment():
print(f'\033[1;32;0m 欢迎{status_dic["username"]}访问评论页面\033[0m')
@auth
def enshrine():
print(f'\033[1;32;0m 欢迎{status_dic["username"]}访问收藏页面\033[0m')
def login_out():
status_dic['username'] = None
status_dic['status'] = False
print('\033[1;32;0m 注销成功 \033[0m')
def exit_program():
global flag
flag = False
return flag
choice_dict = {
1: login,
2: register,
3: article,
4: diary,
5: comment,
6: enshrine,
7: login_out,
8: exit_program,
}
while flag:
print('''
欢迎来到雨落的CSDN首页
1:请登录
2:请注册
3:文章页面
4:日记页面
5:评论页面
6:收藏页面
7:注销
8:退出程序''')
choice = input('请输入您选择的序号:').strip()
if choice.isdigit():
choice = int(choice)
if 0 < choice <= len(choice_dict):
choice_dict[choice]()
else:
print('\033[1;31;0m 您输入的超出范围,请重新输入 \033[0m')
else:
print('\033[1;31;0m 您您输入的选项有非法字符,请重新输入 \033[0m')
18.1 程序配置
def login():
i = 0
with open('register', encoding='utf-8') as f1:
dic = {i.strip().split('|')[0]: i.strip().split('|')[1] for i in f1}
def register():
with open('register', encoding='utf-8') as f1:
dic = {i.strip().split('|')[0]: i.strip().split('|')[1] for i in f1}
if 6 <= len(password) <= 14:
with open('register', encoding='utf-8', mode='a') as f1:
f1.write(f'\n{username}|{password}')
上面这个项目中所有的有关文件的操作出现几处,都是直接写的register相对路径,如果说这个register注册表路径改变了,或者你改变了register注册表的名称,那么相应的这几处都需要一一更改,这样其实你就是把代码写死了
那么这时就要统一相同的路径,也就是统一相同的变量,在文件的最上面写一个变量指向register注册表的路径,代码中如果需要这个路径时,直接引用即可
18.2 划分文件
一个具体的实际的项目,函数会非常多,所以我们应该将这些函数进行分类,然后分文件而治。在这里划分了以下几个文件:
1、settings.py:配置文件,就是放置一些项目中需要的静态参数,比如文件路径,数据库配置,软件的默认设置等
register_path = r'C:/Users/yuluo/PycharmProjects/雨落-Python/雨落教学/yuluoCSDN.py'
2、common.py:公共组件文件,这里面放置一些我们常用的公共组件函数,并不是我们核心逻辑的函数,而更像是服务于整个程序中的公用的插件,程序中需要即调用。比如我们程序中的装饰器auth,有些函数是需要这个装饰器认证的,但是有一些是不需要这个装饰器认证的,它既是何处需要何处调用即可。比如还有密码加密功能,序列化功能,日志功能等这些功能都可以放在这里
def auth(func):
def inner(*args, **kwargs):
if status_dic['status']:
ret = func(*args, **kwargs)
return ret
else:
print('\033[1;31;0m 请先进行登录 \033[0m')
if login():
ret = func(*args, **kwargs)
return ret
return inner
3、src.py:这个文件主要存放的就是核心逻辑功能,这些核心功能函数,都应该放在这个文件中
def login():
pass
def register():
pass
@auth
def article():
pass
@auth
def diary():
pass
@auth
def comment():
pass
@auth
def enshrine():
pass
def login_out():
pass
def exit_program():
pass
4、start.py:项目启动文件。你的项目需要有专门的文件启动,而不是在你的核心逻辑部分进行启动的,目的就是放在显眼的位置,方便开启
while flag:
print('''
欢迎来到雨落的CSDN首页
1:请登录
2:请注册
3:文章页面
4:日记页面
5:评论页面
6:收藏页面
7:注销
8:退出程序''')
choice = input('请输入您选择的序号:').strip()
if choice.isdigit():
choice = int(choice)
if 0 < choice <= len(choice_dict):
choice_dict[choice]()
else:
print('\033[1;31;0m 您输入的超出范围,请重新输入 \033[0m')
else:
print('\033[1;31;0m 您您输入的选项有非法字符,请重新输入 \033[0m')
但是上面这个文件,看起来还是有些吃操作,那有没有更方便的呢,有的有的兄弟,像这样更方便的那就是整合成函数,一键启动,这个 run() 就是整个文件的开关
def run():
while flag:
print('''
欢迎来到雨落的CSDN首页
1:请登录
2:请注册
3:文章页面
4:日记页面
5:评论页面
6:收藏页面
7:注销
8:退出程序''')
choice = input('请输入您选择的序号:').strip()
if choice.isdigit():
choice = int(choice)
if 0 < choice <= len(choice_dict):
choice_dict[choice]()
else:
print('\033[1;31;0m 您输入的超出范围,请重新输入 \033[0m')
else:
print('\033[1;31;0m 您您输入的选项有非法字符,请重新输入 \033[0m')
run()
5、类似于register文件:这个文件文件名不固定,register只是我们项目中用到的注册表,但是这种文件就是存储数据的文件,类似于文本数据库,那么我们一些项目中的数据有的是从数据库中获取的,有些数据就是这种文本数据库中获取的,总之,你的项目中有时会遇到将一些数据存储在文件中,与程序交互的情况,所以我们要单独设置这样的文件
6、log文件:log文件顾名思义就是存储log日志的文件。日志主要是供开发人员使用,比如你项目中出现一些bug问题,比如开发人员对服务器做的一些操作都会记录到日志中,以便开发者浏览,查询
至此我们合理的划分成了6个文件,但是还是有问题的,如果我们的项目很大,每一个部分相应的一个文件存不下的,比如src主逻辑文件,函数很多,是不是得分成:src1.py、src2.py;而且文本数据库register这个只是一个注册表,如果还有个人信息表,记录表呢? 如果是这样,我们还需划分目录
18.3 划分具体目录
规范化能更好的控制程序结构,让程序具有更高的可读性;我们设计一个层次清晰的目录结构,就是为了达到以下两点:
1、可读性高:不熟悉这个项目的代码的人,一眼就能看懂目录结构,知道程序启动脚本是哪个,测试目录在哪儿,配置文件在哪儿等等。从而非常快速的了解这个项目
2、可维护性高:定义好组织规则后,维护者就能很明确地知道,新增的哪个文件和代码应该放在什么目录之下。这个好处是,随着时间的推移,代码/配置的规模增加,项目结构不会混乱,仍然能够组织良好
目录结构:
1、bin文件夹下对应start.py
2、conf文件夹下对应settings.py
3、core文件夹下对应src.py
4、db文件夹下对应register (txt)
5、lib文件夹下对应common.py
6、log文件夹下对应access.log (txt)
7、此外应该还包含README
关于README:
1、软件定位,软件的基本功能
2、运行代码的方法: 安装环境、启动命令等
3、简要的使用说明
4、代码目录结构说明,更详细点可以说明软件的基本原理
5、常见问题说明
在软件开发初期,由于开发过程中内容可能不明确或者发生变化,并不是一定要在一开始就将所有信息都补全。但是在项目完结的时候,是需要撰写这样的一个文档的,目的是能简要描述该项目的信息,让读者快速了解这个项目。可以参考Redis源码中Readme的写法,这里面简洁但是清晰的描述了Redis功能和源码结构
18.4 包
18.4.1 包的定义
-
包是一种通过使用
.模块名
来组织python模块名称空间的方式 -
具体的:包就是一个包含有
__init__.py
文件的文件夹,所以其实我们创建包的目的就是为了用文件夹将文件/模块组织起来 -
需要强调的是:
-
1、在python3中,即使包下没有
__init__.py
文件,import 包仍然不会报错,而在python2中,包下一定要有该文件,否则import 包报错 -
2、创建包的目的不是为了运行,而是被导入使用,记住,包只是模块的一种形式而已,包的本质就是一种模块
-
-
为何要使用包
包的本质就是一个文件夹,那么文件夹唯一的功能就是将文件组织起来。随着功能越写越多,我们无法将所以功能都放到一个文件中,于是我们使用模块去组织功能,而随着模块越来越多,我们就需要用文件夹将模块文件组织起来,以此来提高程序的结构性和可维护性
18.4.2 注意事项
-
1、关于包相关的导入语句也分为import和from ... import ...两种,但是无论哪种,无论在什么位置,在导入时都必须遵循一个原则:凡是在导入时带点的,点的左边都必须是一个包,否则非法。可以带有一连串的点,如item.subitem.subsubitem,但都必须遵循这个原则。但对于导入后,在使用时就没有这种限制了,点的左边可以是包、模块、函数、类(它们都可以用点的方式调用自己的属性)
-
2、import导入文件时,产生名称空间中的名字来源于文件,import 包,产生的名称空间的名字同样来源于文件,即包下的
__init__.py
,导入包本质就是在导入该文件 -
3、包A和包B下有同名模块也不会冲突,如A.a与B.a来自俩个命名空间
未写完,待续……