软件开发目录规范 ATM框架构建
软件开发的目录规范
建立文件夹
为了提高程序的可读性与可维护性,我们应该为软件设计良好的目录结构,这与规范的编码风格同等重要。软件的目录规范并无硬性标准,只要清晰可读即可
以ATM购物车项目为例:
首先需要建立一个项目文件夹,例如项目名称ATM,这个ATM的大文件夹下需要有几个子文件夹:
bin文件夹:用于存放指令文件,可执行指令,例如:start.py
conf文件夹:配置性的文件,存放一些变量,运行软件时需要从中读取一些变量值,例如:settings.py
db文件夹:放数据库相关的操作代码,例如:db_handle.py
core文件夹:核心代码,例如:src.py
log文件夹:存放日志,查看软件运行时记录的一些日志数据,例如:access.log
lib文件夹:库目录,
db文件夹:存放数据
readme.md:类似于软件说明书
搭框架
在core文件夹下建立src.py文件
==============================在 ATM/core/src.py下================================
def login():
print('登录功能') # 简易的功能
def register():
print('注册功能')
def transfer():
print('转账功能')
function_dic = {
'1':['登录',login],
'2':['注册',register],
'3':['转账',transfer]
}
def run(): # src文件只负责功能性的定义,不负责程序的运行,到时候调用放在start.py文件里面
while True:
print('0 ?? 退出')
for k in function_dic:
print('%s ?? %s' %(k,function_dic[k][0]))
choice = input('输入功能指令>>>')
if choice == '0':
break
if choice in function_dic:
function_dic[choice][1]()
else:
print('指令不存在,重新键入')
在bin文件夹下建立start.py文件
==============================在 ATM/bin/start.py下================================
# 为了软件便于管理维护我们将软件进行拆分
# 让 start.py 能够跨文件调的到 src.py 里面的函数 run,那我们就需要将src文件的文件夹路径加入环境变量
import sys
sys.path.append(r'/Users/jessewu/PycharmProjects/pythonProject/test01/study miscellaneous/规范目录/ATM/core') # 将src文件的文件夹路径加入环境变量
import src # 这样就能正常跨文件调用src的功能了
if __name__ == '__main__': # 当这个文件作为主文件运行时就执行里面的子代码 在执行文件里面加上这个更规范一点
src.run()
但是上述代码仍是有问题的因为我们可能会导入到的文件很多,那我们每次导入都需要添加到环境变量,这样就比较麻烦,我们改进一下:
==============================在 ATM/bin/start.py下================================
import sys
sys.path.append(r'/Users/jessewu/PycharmProjects/pythonProject/test01/study miscellaneous/规范目录/ATM') # 将文件的软件根目录直接加入环境变量 这样就能用到哪个直接导入
from core import src
if __name__ == '__main__':
src.run()
这样我们又碰到另一个问题,我们写的是一个软件,当用户将这个软件安装到其它电脑的时候,文件路径和我们写的时候不一样,那这样软件就无法运行。我们需要再改动一下,将文件路径写活了,让它在别的电脑也能运行:
==============================在 ATM/bin/start.py下================================
import sys
import os # 导入os模块 调用__file__功能,它能够直接动态获取到当前文件的绝对路径
BASE_DIR = os.path.dirname(os.path.dirname(__file__)) # 获取但前文件文件夹的文件夹,也就是副副文件夹,
sys.path.append(BASE_DIR) # 这样就能够动态获取
from core import src
if __name__ == '__main__':
src.run()
但还有更省事的办法:
==============================在 ATM/start.py下================================
# 直接将start.py文件移动到项目的根目录下面,因为当前执行文件的文件夹会自动添加到环境变量中,这样我们就不用手动添加路径了
from core import src # 直接就能导入
if __name__ == '__main__':
src.run()
接下来我们有一个需求,日志需求,我们需要记录日志,日志功能不是用户需要用到的,他是一个其他功能都可能需要调用到的功能,我们将它放在lib文件夹下,创建文件common.py:
==============================在 ATM/lib/common.py下================================
import time
def logger(msg):
with open(r'/Users/jessewu/PycharmProjects/pythonProject/test01/study miscellaneous/规范目录/ATM/log/access.log',\
mode='at',encoding='utf8')as f:
f.write('%s %s\n' %(time.strftime('%Y-%m-%d %H:%M:%s'),msg))
# 简易的日志功能
==============================在 ATM/core/src.py下================================
from lib.common import logger # 需要调用到lib下common里面的logger功能,需要导入一下,需要注意的是这里的.是路径分隔符的意思
def login():
print('登录功能')
logger('登陆了')
def register():
print('注册功能')
logger('有人注册了')
def transfer():
print('转账功能')
logger('转出了一笔钱')
function_dic = {
'1': ['登录', login],
'2': ['注册', register],
'3': ['转账', transfer]
}
def run():
while True:
print('0 ?? 退出')
for k in function_dic:
print('%s ?? %s' % (k, function_dic[k][0]))
choice = input('输入功能指令>>>')
if choice == '0':
logger('下线了')
break
elif choice in function_dic:
function_dic[choice][1]()
else:
print('指令不存在,重新键入')
虽然日志功能能正常记录了,但是还是老问题,记录日志的文件路径被我们写死了,需要将路径写活,且这个路径能够让用户能够配置的,在conf文件夹下创建文件setting.py:
==============================在 ATM/conf/settings.py下================================
# 需要定义一个路径变量,让它在common模块里面能够被调用到
import os
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
LOG_PATH = '%s/log/access.log' % BASE_DIR # 保证用户即自己能改,也可以不改就能用
==============================在 ATM/lib/common.py下================================import timefrom conf import settings # 调用settings里面的logger功能def logger(msg): with open('%s' %ssettings.LOG_PATH,mode='at',encoding='utf8')as f: f.write('%s %s\n' %(time.strftime('%Y-%m-%d %H:%M:%s'),msg))
升级需求,将写好的logger替换成系统里的logging模块,首先我们在settings.py里面定义好LOGGING_DIC,规定好日志的格式和打印形式,过滤级别,logger表示级别为每条日志打上标签,filters表示过滤,handlers表示将日志如何处理,是打印还是输出到终端:
==============================在 ATM/conf/settings.py下================================# 1、定义三种日志输出格式,日志中可能用到的格式化串如下# %(name)s Logger的名字# %(levelno)s 数字形式的日志级别# %(levelname)s 文本形式的日志级别# %(pathname)s 调用日志输出函数的模块的完整路径名,可能没有# %(filename)s 调用日志输出函数的模块的文件名# %(module)s 调用日志输出函数的模块名# %(funcName)s 调用日志输出函数的函数名# %(lineno)d 调用日志输出函数的语句所在的代码行# %(created)f 当前时间,用UNIX标准的表示时间的浮 点数表示# %(relativeCreated)d 输出日志信息时的,自Logger创建以 来的毫秒数# %(asctime)s 字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒# %(thread)d 线程ID。可能没有# %(threadName)s 线程名。可能没有# %(process)d 进程ID。可能没有# %(message)s用户输出的消息# 2、强调:其中的%(name)s为getlogger时指定的名字standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' \ '[%(levelname)s][%(message)s]'simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'test_format = '%(asctime)s] %(message)s'LOGGING_DIC = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'standard': { 'format': standard_format }, 'simple': { 'format': simple_format }, 'test': { 'format': test_format }, }, 'filters': {}, 'handlers': { #打印到终端的日志 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', # 打印到屏幕 'formatter': 'simple' }, #打印到文件的日志,收集info及以上的日志 'default': { 'level': 'DEBUG', 'class': 'logging.handlers.RotatingFileHandler', # 保存到文件,日志轮转 'formatter': 'standard', # 可以定制日志文件路径 # BASE_DIR = os.path.dirname(os.path.abspath(__file__)) # log文件的目录 # LOG_PATH = os.path.join(BASE_DIR,'a1.log') 'filename': LOG_PATH, # 日志文件 'maxBytes': 1024*1024*5, # 日志大小 5M 'backupCount': 5, 'encoding': 'utf-8', # 日志文件的编码,再也不用担心中文log乱码了 }, 'other': { 'level': 'DEBUG', 'class': 'logging.FileHandler', # 保存到文件 'formatter': 'test', 'filename': 'a2.log', 'encoding': 'utf-8', }, }, 'loggers': { #logging.getLogger(__name__)拿到的logger配置 '用户相关日志': { 'handlers': ['default', 'console'], # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕 'level': 'DEBUG', # loggers(第一层日志级别关限制)--->handlers(第二层日志级别关卡限制) 'propagate': False, # 默认为True,向上(更高level的logger)传递,通常设置为False即可,否则会一份日志向上层层传递 }, },}
定义好之后,我们到原本的logger功能模块common.py下面将之前的蹩脚功能注释掉,重新写,定义logger函数:
==============================在 ATM/lib/common.py下================================
from conf import settings
import logging.configlogging.config.dictConfig(settings.LOGGING_DIC) # 固定用法def logger(log_name): logger1 = logging.getLogger(log_name) # log_name 使用者需要自己传日志类型名称进去 return logger1
==============================在 ATM/core/src.py下================================
import loggingfrom lib.common import loggerdef login(): print('登录功能') log = logger('用户相关日志') # 赋值给变量 log.debug('xxx登录了') # 调用级别def register(): print('注册功能') log = logger('用户相关日志') log.info('有人注册了')def transfer(): print('转账功能') log = logger('用户相关日志') log.info('转出了一笔钱')def withdraw(): print('提现功能') log = logger('用户相关日志') log.warning('提现金额过大,可能要跑')function_dic = { '1': ['登录', login], '2': ['注册', register], '3': ['转账', transfer], '4': ['提现', withdraw]}def run(): while True: print('0 ?? 退出') for k in function_dic: print('%s ?? %s' % (k, function_dic[k][0])) choice = input('输入功能指令>>>') if choice == '0': log = logger('用户相关日志') log.info('下线了') break elif choice in function_dic: function_dic[choice][1]() else: print('指令不存在,重新键入')