一、异常处理机制
异常的定义:程序运行时发生的不正常事件。
一般情况下,程序遇见异常bug就会终止运行,试想一下现实世界中,程序遇见小小的bug就直接死机,可以吗?——很不好。
怎么解决呢?
使用异常处理机制,捕获异常,处理异常。
异常分为:内置异常、自定义异常。
1.1 常见的内置异常
# print(name) # NameError: name 'name' is not defined
# print(int('100a'))
# ValueError: invalid literal for int() with base 10: '100a'
# print('hello' + 1234)
# TypeError: can only concatenate str (not "int") to str
# ls = [1] # print(ls[10])
# IndexError: list index out of range
# if 1: # print("我是真的")
# IndentationError: expected an indented block after 'if' statement on line 14
# s1 = 'hello'
# print(s1.name)
# AttributeError: 'str' object has no attribute 'name'
# print(3/0)
# ZeroDivisionError: division by zero
# import hahaha # ModuleNotFoundError: No module named 'hahaha'
# f = open('hahaha.txt', 'r')
# FileNotFoundError: [Errno 2] No such file or directory: 'hahaha.txt'
1.2 内置异常处理
异常处理是对异常进行捕获、抛出、处理,提高程序健壮性的机制。
算法的设计要求:正确性、可读性、健壮性、高效率、低存储
使用关键字try except finally else组合成不同的处理方式,其中try和except是核心。
简单模式:
语法
try:
代码1
except:
代码2
例子:
try:
print(name)
except:
print('命名错误')
捕获多个异常结构
语法:
try:
代码块
except XXX:
代码2
except XXX:
代码3
except XXX:
代码4
例子:
try:
print('hello' + 1234)
except NameError:
print("命名错误")
except TypeError:
print("类型错误")
except IndentationError:
print("缩进错误")
try:
print('hello' + 1234)
except (NameError, TypeError, IndentationError):#从中匹配一个
print("有错误")
try:
print('hello' + 1234)
except Exception as e:
print(f"有{e}错误")
try...except...else结构
语法:
try:
代码1
except:
代码2
else:
代码3
例子:
try:
a = 3
b = 0
c = a/b
except Exception as f:
print(f) #division by zero
else:
print(c)
try...except...else...finally结构
语法:
try:
代码1
except:
代码2
else:
代码3
finally:
代码4
例子:
try:
a = 3
b = 0
c = a/b
except Exception as f:
print(f) #division by zero
else:
print(c)
finally:
print("我会一直在")
练习: 1、使用异常处理,定义一个函数func(listinfo)listinfo为列表, listinfo = [133, 88, 24, 33, 232, 44, 11, 44], 返回列表中小于100且为偶数的数。
listinfo = [133, 88, 24, 33, 232, 44, 11, 44]
try:
l = [i for i in listinfo if i%2==0 and i<100]
except Exception as e:
print(e)
else:
print(l) #[88, 24, 44, 44]
# finally:
# print('就这样')
1.3 自定义异常
python放入内置异常已经很丰富了,但是我们还是可以自定义异常的,也就是说我们可以根据自己的实际需求做异常判断。
在学习自定义异常之前,先来看一下 raise的使用。
raise可以手动抛出异常——内置异常或者自定义异常都可以。
def sub(a, b):
#定义一个两个数商的函数(当除数为0报出错误)
def sub(a,b):
if b == 0:
raise Exception('除数不能等于0')
else:
return a/b
try:
print(sub(2,0))
except Exception as e:
print(e) #除数不能等于0
现在来看一下自定义异常类,自定义异常必须继承Exception类
语法格式:
class MyException(BaseException):
def __init__(self, msg):
self.msg = msg
def __str__(self):
return self.msg
try:
raise MyException('异常消息字符串')
except MyException as e:
print(e)
class error(BaseException):
def __init__(self,num):
self.num = num
def __str__(self):
return self.num
def sub(a,b):
if a<b:
raise error('被减数不能小于减数')
else:
return a-b
try:
print(sub(2,3))
except Exception as e:
print(e)
练习:
编写一个 Drugs 药品类,添加一个类属性,产地 place 为江中集团 添加一个类方法,方法名自定义,实现修改类属性的值,将产地修改为中国 添加构造方法,实例属性如下:name(名称),out_time(过期时间)默认值为 2024-09-16 添加查询剩余保质期的方法 get_time,输出当前时间与过期时间的差值,单位为天, 使用异常捕获差值信息,如果是大于等于 0 的话,则正常输出,如果小于 0 的话, 则抛出异常,异常信息为当前药品已过期,并捕获该异常
class Drugs:
place = '江中集团'
def __init__(self,name,out_time = 50):
self.name = name
self.out_time = out_time
@property
def id(self):
return f"产地{self.__id}"
@id.setter
def id(self,new_id):
self.__id = new_id
def get_time(self,new_time):
self.out_time-=new_time
class error(BaseException):
def __init__(self,num):
self.num = num
def __str__(self):
return self.num
n = Drugs('阿莫西林')
def a(x):
n.get_time(x)
if n.out_time<0:
raise error('已经过期不能吃了')
else:
print(f'药还剩{n.out_time}天')
try:
a(60)
except error as e:
print(e)
1.5 demo2:日志的简单应用
在python中,记录日志使用logging库,日志的级别从高到低分别为:
- CRITICAL:系统崩溃级别的错误,必须立即处理
- ERROR:运行时的错误,可能导致程序无法正常运行
- WARNING:警告信息
- INFO:信息性消息,程序正常运行
- DEBUG:详细信息,通常在诊断问题时有用
import logging
# 设置日志的打印级别
# logging.basicConfig(level=logging.DEBUG)
logging.critical('这是一个critical信息')
logging.error('这是一个error信息')
logging.warning('这是一个warning信息')
logging.info('这是一个info信息')
logging.debug('这是一个debug信息')
# 向指定的日志文件里去打印日志信息
import logging
# logging.basicConfig(filename='./app.log')
# logging.warning('这是一个warning信息')
# logging.basicConfig(filename='./app.log', level=logging.DEBUG, filemode='a',
# format='%(name)s - %(levelname)s - %(message)s')
# logging.warning('这是一个warning信息')
# %(name)s 日志记录器的名字
# %(levelname)s 日志级别
# %(asctime)s 时间
# %(message)s 消息本身
二、文件操作
2.0 文件操作的重要性和应用场景
重要性
- 数据持久化
- 跨平台兼容性
- 数据备份与恢复
- 数据共享
- 配置管理
- 日志记录
应用场景
- 数据分析
- web开发
- 文本处理
2.1 文件的基本概念
2.1.1 文件的概念
文件是一个存储在某种持久性存储介质【硬盘、光盘、磁盘等】上的数据的结合。
文件可以包含各种类型的信息:文本、图像、音频、视频、应用程序代码以及其他类型的二进制数据。
文件通常由数据、元数据、文件系统等几部分组成。
文件的属性有:文件名、位置、文件类型、文件大小、创建日期、修改日期、访问权限。
2.1.2 文件的分类
windows系统下大致分为以下几种:
- 文本文件:包含可读字符的文件,如.txt .csv .py .html等
- 二进制文件:包含不可直接读的原始二进制数据的文件,如.exe .jpg .mp3等
- 可执行文件:可以被操作系统执行的文件,如.exe
- 数据文件:用于存储应用程序数据的文件,如数据库文件、配置文件等
- 目录/文件夹:用于组织和管理其他文件的特殊文件
在Linux系统下,有以下几种:
- - 普通文件
- d 目录文件
- b 块设备文件【底层驱动文件】
- c 字符设备文件【底层驱动文件】
- l 链接文件【类似于快捷方式】
- p 管道文件,用于进程间的通信
- s 套接字文件,用于网络通信的端点
文本文件:
- 文本文件是由单一特定编码组成的文件,如UTF-8编码。
- 由于存在编码,文本文件也被看成是存储着数据的长字符串。
二进制文件:
- 直接由比特0和1组成,没有统一字符编码
- 一般都存在二进制0和1的组织结构,即文件格式
s = 'hello world!'
print("文本文件:", s)
print("二进制文件:", s.encode())
2.1.3 文件位置【路径】
在计算机系统中,路径是用来表示文件位置的一种方式。
路径又分绝对路径和相对路径。
windows系统下:
绝对路径:带有盘符的或者带有网址的
C:\Users\admin\Desktop\py_gj\py_day3
相对路径:
./py_gj/py_day3
../py_gj/py_day3
这里的点有特殊含义,一个点表示当前路径下,两个点表示上一级路径下。
path = "C:\\Users\\admin\\Desktop\\py_gj\\py_day3" print(path)
path1 = './day3.3.py' print(path1) #本级目录
path2 = "../py_day2/day2.2.py" print(path2)#上一级目录
2.2 文件的操作
2.2.1 打开文件open
如果想用open读取文件,那么先来看一下它的语法格式:
open(file, [mode='r', buffering=-1, encoding=None])
参数:
- file: 必需,文件路径(相对或者绝对路径)
- mode: 可选,文件打开模式,默认为r
- buffering: 可选,设置缓冲
- encoding: 可选,一般使用utf-8
返回值:一个文件对象
mode常用的模式:
打开方式 | 功能 | 文件存在时 | 文件不存在时 |
r | 只读方式打开文件 | 打开 | 报错 |
r+ | 以读写方式打开文件 | 打开 | 报错 |
w | 只写方式打开文件 | 打开(清空之前内容) | 新建 |
w+ | 以读写方式打开文件 | 打开(清空之前内容) | 新建 |
a | 以追加方式打开文件(写) | 打开,保留之前内容 | 新建 |
a+ | 以读写方式打开文件 | 打开,保留之前内容 | 新建 |
mode参数还可以指定以什么样的编码方式读写文本,默认情况下open是以文本形式打开文件的,比如上面的四种mode模式。
当我们需要以"字节[二进制]"形式读写文件时,只需要在mode参数的后面加 b 就可以了。
- rb 以二进制形式打开一个文件,只读
- wb 以二进制形式打开一个文件,只写
- wb+ 以二进制形式打开一个文件,读写
demo1:读取文件
- 我们在同级目录下创建一个名为111的txt文本文件,同时创建一个test.py文件,通过代码去读取其中的内容。
- 我们在111.txt中写入两行内容: Life is short, You need Python 人生苦短,我用python
- 然后在test.py中写入读取代码
2.2.2 with关键字
当我们使用open直接打开文件,进行文件操作后,需要使用close进行文件保存和关闭。但是这种方式不是最好的,为什么呢?因为在文件操作过程中一旦有报错,那么文件还能正确关闭吗?——不能
如何解决呢?——使用 with 关键字
优点:当我们操作文件的代码块结束后,文件会正确关闭,即便文件操作报错,也会正确关闭,无需手动调用close()
with open('./111.txt', encoding='utf-8') as f:
read_data = f.read()
print(read_data)
2.2.3 关闭文件close()
语法格式:
file.close()
2.2.4 read()方法
语法格式:
f.read(size) # f为文件对象
参数:
- size(可选):为数字,表示从已打开文件中读取的字节计数,默认值为-1,默认情况下为读取全部。
with open('./111.txt', encoding='utf-8') as f:
read_data = f.read(5)
print(read_data)
2.2.5 readline()方法与readlines()方法
readline()方法
注意: readline()方法会记住上一个readline()读取的位置,接着读取下一行。
readlines()方法
2.2.6 write()方法
在python中使用write()方法将字符串写入文件中。
f.write(str) # f为文件对象
参数:
- str代表要写入的字符串
demo:
2.2.7 文件指针定位
python中文件读写,定位文件读写的指针会随着不断地读写而移动。所以我们想要正确的读写,就需要控制指针的定位。
f.tell()
功能:返回文件的当前位置,即文件指针当前位置
f.seek(offset[, whence])
功能:将文件内部光标定位到指定的位置
参数:offset:开始的偏移量,也就是代表需要移动偏移的字节数
whence:可选,默认值为 0,
0代表从文件开头开始算起
1代表从当前位置开始算起
2代表从文件末尾算起
eg:
f.seek(p,0) 移动到文件第p个字节处,绝对位置
f.seek(p,1) 移动到相对于当前位置之后的p个字节
f.seek(p,2) 移动到相对文章尾之后的p个字节
f.seek(0,0) 移动到文件开头
s = "Hello, world!"
with open('./binary.txt', mode='wb+') as f:
f.write(s.encode())
f.seek(2,0) # 从开头 偏移2个
f.seek(2,1) # 从当前 偏移2个
f.seek(2,2) # 从末尾 偏移2个
f.seek(0,0)
data = f.read(1)
print(data)
2.3 目录的操作
import os
try:
os.mkdir('./t1')
except Exception as e:
print(e)
try:
os.rmdir('./t1')
except Exception as e:
print(e)
try:
os.makedirs('./t1/t2')
except Exception as e:
print(e)
练习:将 1-100 之间能被 5 整除的数保存在列表 numbers 中 1. 将 numbers 中的元素写入到文件 num.txt 中 2. 使用文件操作方式打开 num.txt 文件读取内容,并计算它们的平均数
# 将 1-100 之间能被 5 整除的数保存在列表 numbers 中
# 1. 将 numbers 中的元素写入到文件 num.txt 中
# 2. 使用文件操作方式打开 num.txt 文件读取内容,并计算它们的平均数
l = [i for i in range(1,101) if i%5==0]
with open('./num.txt',mode='w') as f:
for i in l:
f.write(str(i)+'\n')
with open('./num.txt',mode='r') as f:
sum = 0
for i in f.readlines():
sum+=int(i)
print(sum/len(l))#52.5