对上节实现的学生信息管理系统进行重构,要求实现日志功能和异常处理功能。
使用系统自带库 logging 和 shelve,不引入第三方库
导入 sys 包,使用 sys.exit( ) 可退出程序。
数据要求的改变:学生的身高和体重使用数字类型存储。
学生信息管理系统v1.0
学生信息管理系统v1.1
学生信息管理系统v1.2
本次升级所用到的python知识
Python 的语法错误或称为解析错误,经常被初学者所碰到
count = 0
while (count < 10)
print('本次的count是: ',count)
count += 1
print('count输出结束。')
即便 Python 程序语法是正确的,在运行时也有可能出现错误,在运行时检测到的错误称为异常
print(10*(1/0))
运行时会产生如下异常,因为 0 不能作为除数
因为在程序中难免会碰到此类问题,所以在程序中必须对可能发生的错误进行处理,否则程序会因为各种问题终止并退出。
而 Python 内置了一套异常处理,来帮助我们进行错误的处理。
当我们认为某些代码可能会出错时,就可以用 try 来运行这段代码,如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即 except 语句块。
以下为拦截 0 作为除数发生的异常:
try:
print('try...')
r = 10 / 0
print('result:', r)
except ZeroDivisionError as e:
print('except:', e)
finally:
print('finally...')
print('END')
运行后结果如下:
从输出结果可见,当错误发生后,后续语句 print(‘result:’, r) 不会被执行,expect 由于捕获到 ZeroDivisionError,因此被执行,然后程序继续走,执行 finally。
- BaseException # 所有异常的父类
- SystemExit # 由sys.exit()抛出的异常
- KeyboardInterrupt # 通常由ctrl+c或者Delete抛出的异常
- GeneratorExit # 当生成器被关闭时抛出的异常
- Exception #
- StopIteration # 迭代结束异常
- StopAsyncIteration # 由异步迭代的`__anext__()`抛出的异常
- ArithmeticError # 各种算数错误引起的异常
- FloatingPointError # 浮点数操作错误
- OverflowError # 结果超出范围
- ZeroDivisionError # 0为除数异常
- AssertionError # assert错误异常
- AttributeError # 属性引用异常
- BufferError # 缓存错误
- EOFError # 读不到数据
- ImportError # import错误
- ModuleNotFoundError # 找不多模块
- LookupError # 由索引和key值引起的异常
- IndexError # 索引错误
- KeyError # 字典key值错误
- MemoryError # 内存溢出异常
- NameError # 本地和全局找不到变量名
- UnboundLocalError # 局部变量没有赋值
- OSError # system错误
- BlockingIOError # 调用阻塞异常错误
- ChildProcessError # 子进程
- ConnectionError # 连接
- BrokenPipeError # 管道读写异常
- ConnectionAbortedError # 连接失败
- ConnectionRefusedError # 连接拒绝
- ConnectionResetError # 连接重置
- FileExistsError # 创建文件和文件夹错误
- FileNotFoundError # 文件未找到
- InterruptedError # 中断错误
- IsADirectoryError # 文件操作用在文件夹上
- NotADirectoryError # 不是文件夹
- PermissionError # 权限
- ProcessLookupError # 进程不存在
- TimeoutError # 超时
- ReferenceError # 引用异常
- RuntimeError #
- NotImplementedError # 运行抽象方法
- RecursionError # 超出最大递归深度
- SyntaxError # 语法错误
- IndentationError # 缩进错误
- TabError # tab错误
- SystemError # 解释器中断
- TypeError # 类型错误
- ValueError # 赋值错误
- UnicodeError #
- UnicodeEncodeError # unicode编码错误
- UnicodeDecodeError # unicode解码错误
- UnicodeTranslateError # unicode转换错误
- Warning #
- DeprecationWarning # 操作不赞成警告
- PendingDeprecationWarning # 表明此操作将来会被弃用
- UserWarning # 用于用户生成警告
- SyntaxWarning # 语法可疑警告
- RuntimeWarning # 运行警告
- FutureWarning # 将会改变警告
- ImportWarning # 导入警告
- UnicodeWarning # unicode相关警告
- BytesWarning # 字节相关警告
- ResourceWarning # 资源使用情况警告
1)首先,执行 try 子句(在关键字 try 和关键字 except 之间的语句)。
2)如果没有异常发生,忽略 except 子句,try 子句执行后结束。
3)如果在执行 try 子句的过程中发生了异常,那么 try 子句余下的部分将被忽略。如果异常的类型和 except 之后的名称相符,那么对应的 except 子句将被执行。最后执行 try 语句之后的代码。
4)如果一个异常没有与任何的 except 匹配,那么这个异常将会传递给上层的 try 中。
如果发生了不同类型的错误,应该由不同的 except 语句块处理(类似于多重 if)。
try:
r = 10 / int(input('请输入: '))
except ValueError as e:
print('ValueError:', e )
except ZeroDivisionError as e:
print('ZeroDivisionError:', e)
finally:
pass
print('END')
输入 a 运行:
输入0运行:
如果没有错误发生,可以在 except 语句块后加一个 else,当没有错误发生时会自动执行 else 语句。
try:
r = 10 / int(input('请输入: '))
except ValueError as e:
print('ValueError:', e )
except ZeroDivisionError as e:
print('ZeroDivisionError:', e)
else:
print('r =', r)
finally:
pass
print('END')
使用 try…except 捕获错误还有一个巨大的好处,就是可以跨越多层调用,比如函数 main( ) 调用 foo( ),foo( ) 调用 bar( ),结果 bar( ) 出错了,这时,只要 main( ) 捕获到就可以处理。
在异常处理机制中,Python 实现的跨层调用相对于C++、Java比较方便。
def foo(s):
return 10 / int(s)
def bar(s):
return foo(s) * 2
def main():
try:
bar('0')
except Exception as e:
print('Error:', e)
finally:
print('finally...')
if __name__ == '__main__':
main()
也就是说,不需要在每个可能出错的地方去捕获错误,只要在合适的层次去捕获错误就可以了。这样一来,就大大减少了写 try…except…finally 的麻烦。
如果不捕获错误,自然可以让 Python 解释器来打印出错误堆栈,但程序也被结束了。既然我们能捕获错误,就可以把错误堆栈打印出来,然后分析错误原因,同时,让程序继续执行下去。
Python 内置的 logging 模块可以非常容易地记录错误信息:
import logging
def foo(s):
return 10 / int(s)
def bar(s):
return foo(s) * 2
try:
bar('0')
except Exception as e:
logging.exception(e)
print('END')
程序能一次写完并正常运行的概率很小,当遇到复杂 bug 时,我们需要一套调试程序的手段来修复。
logging 日志就是一种很好的处理,logging 不会抛出错误,而且可以输出到文件:
import logging
logging.basicConfig(level = logging.INFO)
s = '0'
n = int(s)
logging.info('n = %d' % n )
print(10 / n)
这就是 logging 的好处,它允许你指定记录信息的级别,有 debug,info,warning,error 等几个级别,当我们指定 level= INFO 时,logging.debug 就不起作用了,其余同理。这样就可以放心地输出不同级别的信息,也不用删除,最后统一控制输出哪个级别的信息。
import logging
logging.basicConfig(
level=logging.INFO, #定义输出到文件的log级别
format='%(asctime)s %(filename)s %(levelname)s %(message)s', #日志格式
datefmt='%Y-%m-%d %A %H:%M:%S', #日期格式 年(大写) 月 日 星期 时 分 秒
filemode='a', #不能用W,被覆盖后查看不到之前录入信息
filename='zuoye8-1.log' #文件名
) # 设定日志
代码如下:
# coding: utf-8
# Time: 2021/9/15 15:37
# Author: pinkward
# Software: PyCharm
# 此代码仅在系统第一次运行前执行,做数据文件的初始化
import shelve # 导入模块
studic = {
'x':{
'name':'pinkward','height(cm)':'180','weight(kg)':'65','class':'x1','tel':'11111111111'}
} # 初始存储数据 子字典形式储存
f = shelve.open('stu') # stu文件
f['studic'] = studic # 存入studic字典
f['gly'] = {
'admin': '123456'} # 存入gly字典
f.close()
# coding: utf-8
# Time: 2021/9/20 14:37
# Author: pinkward
# Software: PyCharm
# 学生信息管理系统v1.3
import shelve
import logging
import sys # python自己的描述环境
logging.basicConfig(
level=logging.INFO, #定义输出到文件的log级别
format='%(asctime)s %(filename)s %(levelname)s %(message)s', #日志格式
datefmt='%Y-%m-%d %A %H:%M:%S', #日期格式 年(大写) 月 日 星期 时 分 秒
filemode='a', #不能用W,被覆盖后查看不到之前录入信息
filename='zuoye8-1.log' #文件名
) # 设定日志
f = shelve.open('stu') # 从文件中读入数据
try:
studic = f['studic']
gly = f['gly'] # 读入数据 如果为空拦截报错
except KeyError as e:
print('数据文件损坏,无法使用')
logging.error('数据文件损坏,无法使用')
f.close()
sys.exit()
finally:
f.close()
def query(): # 查询函数
if len(studic):
for x in studic: # 用for循环访问studic字典
stu = studic[x] # 依据学号 将子字典赋给stu
print('--学号:', x) # 输出键
print('\t姓名: ', stu['name'])
print('\t身高(cm):', stu['height(cm)'])
print('\t体重(kg):', stu['weight(kg)'])
print('\t班级: ', stu['class'])
print('\t电话: ', stu['tel']) # 拿出子字典中的值
else:
print('---当前无学生信息!---')
def insert(): # 添加函数
xh = input('请输入学生学号: ')
if not xh or xh in studic: # 判断系统内是否有该学号
print('---此学号已存在!请重新输入:---')
else: # 学生的各项信息均为必填项
while 1:
name = input('请输入姓名: ')
if name:
break
while 1:
try:
height = int(input('请输入身高: '))
if height and (height > 50 and height < 230):
break
else:
print('身高必须在50~230之间!')
except ValueError as e:
print('身高必须是数字!')
logging.error('身高必须是数字!')
while 1:
try:
weight = int(input('请输入体重: '))
if weight and (weight > 40 and weight < 200):
break
else:
print('体重必须在40~200之间!')
except ValueError as e:
print('体重必须是数字!')
logging.error('体重必须是数字!')
while 1:
bj = input('请输入班级: ')
if bj:
break
while 1:
tel = input('请输入电话: ')
if tel:
break
studic[xh] = {
'name': name, 'height(cm)': height, 'weight(kg)': weight, 'class': bj, 'tel': tel} # 存入字典
print('---已成功录入学生信息!---')
def update(): # 修改函数
xh = input('请输入学生学号: ')
if xh and xh not in studic: # 判断系统内是否有该学号
print('---此学号不存在!请重新输入:---')
else:
while 1:
name = input('请输入姓名: ')
if name:
break
while 1:
try:
height = int(input('请输入身高: '))
if height and (height > 50 and height < 230):
break
else:
print('身高必须在50~230之间!')
except ValueError as e:
print('身高必须是数字!')
logging.error('身高必须是数字!')
while 1:
try:
weight = int(input('请输入体重: '))
if weight and (weight > 40 and weight < 200):
break
else:
print('体重必须在40~200之间!')
except ValueError as e:
print('体重必须是数字!')
logging.error('体重必须是数字!')
while 1:
bj = input('请输入班级: ')
if bj:
break
while 1:
tel = input('请输入电话: ')
if tel:
break
studic[xh] = {
'name': name, 'height(cm)': height, 'weight(kg)': weight, 'class': bj, 'tel': tel} # 存入字典
print('---已成功修改学生信息!---')
def delete(): # 删除函数
xh = input('请输入学生学号: ')
if xh not in studic: # 判断系统是否有该学号
print('---此学号不存在!请重新输入:---')
else:
del studic[xh] # 删除键即删除值 实现删除信息功能
print('---成功删除该学号学生信息!---')
def editmanager(): # 维护函数
while 1:
mname = input('请输入管理员用户名:')
if mname:
break
while 1:
mpwd1 = input('请输入管理员密码: ')
if mpwd1:
break
while 1:
mpwd2 = input('请再次输入管理员密码: ')
if mpwd2:
break
if mname and mpwd2 == mpwd1:
gly.clear()
gly[mname] = mpwd2
print('---维护管理员信息成功!---')
else:
print('---用户名或密码错误!---')
def main(): # main函数
print()
print('欢迎使用学生信息管理系统v1.3!'.center(50))
username = input('请输入用户名: ')
password = input('请输入密码: ')
if username in gly and password == gly[username]:
while 1:
while 1:
print()
print('学生信息管理系统v1.3'.center(50))
print('系统菜单'.center(52))
print('1.添加学生信息'.center(50))
print('2.删除学生信息'.center(50))
print('3.修改学生信息'.center(50))
print('4.查看学生信息'.center(50))
print('5.维护管理员信息'.center(52))
print('6.退出管理系统'.center(50))
opt = input('请选择功能: ')
if not opt or opt not in '123456' or len(opt) > 1:
print('---无效选项!请重新选择:---')
continue
else:
break # 确保正确输入进入功能选项
if opt in '1':
insert() # 调用添加函数
elif opt in '2':
delete() # 调用删除函数
elif opt in '3':
update() # 调用修改函数
elif opt in '4':
query() # 调用查询函数
elif opt in '5':
editmanager() # 调用维护函数
else:
f = shelve.open('stu') # 将现有信息写入文件
f['studic'] = studic
f['gly'] = gly # 存入当前信息
f.close()
break # 退出管理系统
print('---已成功退出!---')
print('感谢使用学生信息管理系统v1.3!'.center(50))
else:
print('---普通用户登录成功!---') # 普通用户页面
while 1:
while 1:
print()
print('欢迎使用学生信息管理系统v1.3!'.center(50))
print('系统菜单'.center(52))
print('4.查看学生信息'.center(50))
print('5.退出管理系统'.center(50))
opt = input('请选择功能: ')
if not opt or opt not in '45' or len(opt) > 1:
print('---无效选项!请重新选择:---')
continue
else:
break # 确保正确输入进入功能选项
if opt in '4':
query() # 调用查询函数
else:
break # 退出管理系统
print('---已成功退出!---')
print('感谢使用学生信息管理系统v1.3!'.center(50))
if __name__ == '__main__': # 判断是否为自主运行
main() # 调用main函数