Python基础——1.4文件读写、文件模式、文件修改、异常捕获、浅拷贝、深拷贝

符号介绍
/根目录
./表示当前目录下
…/表示当前目录上一级目录
.\表示项目文件所在目录
…\表示项目文件所在目录向上一级目录
…\表示项目文件所在目录向上二级目录

应用程序运行产生的数据最先都存放于内存中,若要永久保存则必须保存于硬盘中。而应用程序操作硬件必须通过操作系统,文件就是操作系统提供给应用程序来操作硬盘的虚拟概念,用户或应用程序对文件的操作,就是向操作系统发起调用,然后由操作系统完成对硬盘的具体操作。

一、文件操作

1.资源回收与with上下文管理

打开一个文件包含两部分资源:应用程序的变量f和操作系统打开的文件。在操作完毕一个文件时,必须把与该文件的这两部分资源全部回收,回收方法为:

1、f.close() #回收操作系统打开的文件资源
2del f #回收应用程序级的变量
# del f一定要发生在f.close()之后,否则就会导致操作系统打开的文件无法关闭而占用资源, 而python自动的垃圾回收机制决定了我们无需考虑del f,在操作完毕文件后,一定要记住f.close(),若忘记f.close(),python提供了with关键字来帮我们管理上下文

1、在执行完子代码块后,with 会自动执行f.close()

with open('a.txt','w') as f:
    pass

2、可用用with同时打开多个文件,用逗号分隔开即可

with open('a.txt','r') as read_f,open('b.txt','w') as write_f:  
    data = read_f.read()
    write_f.write(data)

2.指定操作文本文件的字符编码

f = open(...)若打开文本文件且没有为open指定编码时,操作系统会用默认编码去打开文件,在windows下是gbk,在linux下是utf-8。故windows中打开文本一定要指明encoding=‘utf-8’

f = open('a.txt','r',encoding='utf-8')

3.编码与解码文件

  • utf-8 是 unicode 字符集一种编码方式。
  • python3使用unicode字符集,而python2使用ASCII

1)encode()

encode() 方法以指定的编码格式编码字符串为二进制,默认编码为 “utf-8”。

# 语法:
变量名.encode([encoding='utf-8'][,errors='strict'])

# encoding – 可选参数,要使用的编码,默认编码为 "utf-8"。
# errors – 可选参数,设置不同错误的处理方案。默认为 "strict",意为编码错误引起一个UnicodeError。 (在使用encode()方法时,报错原因一般都是errors参数问题,改为‘ignore’就好了)

2)decode()

decode() 方法以指定的编码格式解码二进制为字符串,默认编码为 “utf-8”。

# 语法:
变量名.decode([encoding='utf-8'][,errors='strict'])

# encoding – 可选参数,要使用的编码,默认编码为 "utf-8"。
# errors – 可选参数,设置不同错误的处理方案。默认为 "strict",意为编码错误引起一个UnicodeError。 (在使用encode()方法时,报错原因一般都是errors参数问题,改为‘ignore’就好了)
S = '解析'
S_utf8 = S.encode('utf-8')
S_gbk = S.encode('GBK')
print(S)
print(S_utf8)
print(S_gbk)
print(S_utf8.decode('utf-8'))
print(S_gbk.decode('GBK'))
# 解析
# b'\xe8\xa7\xa3\xe6\x9e\x90'
# b'\xbd\xe2\xce\xf6'
# 解析
# 解析

4.操作文件基本流程

# 1.打开文件,由应用程序向操作系统发起系统调用,操作系统打开该文件
	open(...)
# 2.对应一块硬盘空间,并返回一个文件对象赋值给一个变量f
	f=open('a.txt','r',encoding='utf-8') #默认打开模式就为r
# 3.调用文件对象下的读/写方法,会被操作系统转换为读/写硬盘的操作
	data=f.read()
# 4.向操作系统发起关闭文件的请求,回收系统资源
	f.close()

1)打开

open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
# file:要打开的文件名字(或者说是路径)
# Ctrl+shift+c  绝对路径

# ..  表示上一级\
filename = '../day15/demo.txt'
# filename = 'D:\小川\study\pythonproject\python55\day15\demo.txt'
demo = open(filename)
print(demo)
# 对于路径写的时候 在路径字符串前面加一个r,可以规避反斜杠
打开模式mode效果
r以读方式打开,文件必须存在
w以写方式打开,文件不存在则创建,存在清空原有内容
a以追加模式打开,文件不存在则创建,存在则继续进行写操作
r+以读写模式打开,文件必须存在
w+以读写模式打开文件,不存在则创建,存在清空原有内容
a+追加并可读模式,文件不存在则创建,存在则继续进行写操作
rb以二进制读模式打开 同r
wb以二进制写模式打开 同w
ab以二进制追加模式打开 同a
rb+以二进制读写模式打开 同r+
wb+以二进制读写模式打开 同w+
ab+以二进制读写模式打开 同a+

2)关闭

# 1. close()
# 2. with open (...) as ... :   # 执行完可以自动关闭
filename = r'../day15/demo.txt'
with open(filename) as f:
    print(f.read())
print(f.read())  # 会报错,with执行结束就会自动close关闭掉

3)读取

f.read()  # 读取所有内容,执行完该操作后,文件指针会移动到文件末尾
f.readline()  # 读取一行内容,光标移动到第二行首部
f.readlines()  # 读取每一行内容,存放于列表中
强调:f.read()与f.readlines()都是将内容一次性读入内容,如果内容过大会导致内存溢出,若还想将内容全读入内存,则必须分多次读入,有两种实现方式:
方式一
with open('a.txt',mode='rt',encoding='utf-8') as f:
    for line in f:
        print(line) # 同一时刻只读入一行内容到内存中
方式二
with open('1.mp4',mode='rb') as f:
    while True:
        data=f.read(1024) # 同一时刻只读入1024个Bytes到内存中
        if len(data) == 0:
            break
        print(data)

举例介绍

# 设demo2.txt中:
4
4
4
4
# 文件分类:
# 1.纯文本文件(使用utf-8等编码编写的文本文件)
# 2.二进制文件(音乐,图片,视频)
# read()  读取,括号里有参数,指定读取字符的数量,默认值是-1就是读取文件中所有的字符
	filename = r'demo2.txt'  
	with open(filename,encoding='utf-8') as f:
    	content = f.read(4)  # 读4个字符并返回所读字符,换行符\n也是一个字符
    	content = f.read(4)  # 再读4个字符并返回所读字符
    	content = f.read(4)  # 若读取完了文件还继续读,就会返回空字符串
   		print(content) # “空格”
    	print(len(content)) # 0
# 读取一个大文件的规范写法:结果会返回文本的所有内容
	filename = r'demo2.txt'
	with open(filename,encoding='utf-8') as f:
    	all_content = ''
    	while True:
        	content = f.read(3)
        	if content == '':
            	break
        	all_content+=content
	print(all_content)
	# 4
    # 4
    # 4
    # 4
# readline() 读取一行
	filename = r'demo2.txt'
	with open(filename,encoding='utf-8') as f:
    	# 因为print自带换行 ,所以用参数end格式化换行,否则返回字符会换行,既隔一行返回所读内容
    	print(f.readline(),end="")  # 读取一行内容并返回所读字符
    	print(f.readline(),end="")  # 再读一行内容并返回所读字符
        # 4
        # 4
        print(f.readline())
        print(f.readline())
        # 4
		#
		# 4
        #
# readlines()
	filename = r'demo2.txt'
	with open(filename,encoding='utf-8') as f:
    	# 因为print自带换行 ,所以用参数end格式化换行
    	l = f.readlines()  # 一行一行的读完内容
    	print(l[1:3])
        # ['4\n', '4\n']   将每一行作为一个元素返回
        print(l[0:3])
        # ['4\n', '4\n', '4\n']
        print(l[0:3],end="")
        # ['4\n', '4\n', '4\n']

4)写入

f.write('1111\n222\n')  # 针对文本模式的写,需要自己写换行符
f.write('1111\n222\n'.encode('utf-8'))  # 针对b模式的写,需要自己写换行符
f.writelines(['333\n','444\n'])  # 文件模式
f.writelines([bytes('333\n',encoding='utf-8'),'444\n'.encode('utf-8')]) # b模式
# python write和writelines的区别
# file.write(str)的参数是一个字符串,就是你要写入文件的内容.
# file.writelines(sequence)的参数是序列,比如列表,它会迭代帮你写入文件

详细介绍

write()  # 括号里面传递的是字符串
open(file,mode='r')
r:可读,读取文本文件(默认)
w:可写,会默认覆盖文件,如果文件不存在的话就会创建文件
a:追加,如果文件不存在也会创建,如果文件存在则追加
b:读取二进制文件
write也有返回值:返回值就是你写入的字符串长度
filename = 'demo02.txt'
with open(fileName,'w',encoding='utf-8') as f:
    f.write('hello,world\n')
    f.write('hello,world')
filename = 'demo02.txt'
with open(filename,'a',encoding='utf-8') as f:
    f.write('\n你好世界')

5.操作文件的方法

1)__name__

很多Python源码中,会有一句if __name__ == '__main__':(如图)。

a={"1":[1,3], "2":[3,5], "3":[4,6]}
b= "abcdefghijk"
c="de"
def matchTest(key):
    res = eval('b'+str(a[key]).replace(',',':'))
    print(res)
if __name__ == '__main__':
    matchTest('1')

# __name__是python的一个内置类属性,它天生就存在于一个 python 程序中。

直接运行python程序时__name__的值为“__main__”。而在其它程序中导入.py文件运行时,__name__的值为文件名,即模块名。因此__name__的作用是区分py文件直接被运行还是被引入其他程序中。

print(__name__)
# __main__

项目需求生成组合列表的模块,程序编写过程,通常会在程序中写上一些测试脚本来验证程序是否正确。

from itertools import combinations
def build_combination(n, k):
    result = combinations(range(n), k)
    return result
# --------------------------------------
# 以下为针对模块的测试信息
a = build_combination(4, 3)
for i in a:
    print(i)
# (0, 1, 2)
# (0, 1, 3)
# (0, 2, 3)
# (1, 2, 3)

那么在调用这个模块的时候,测试内容也会打印出来。只要在测试代码前面加上:if __name__ == '__main__':编写调试过程直接运行该模块时__name__ 的值为__main__ ,即测试内容被执行。而在导入模块__name__ 的值为py文件名,测试内容则不会被执行。

from itertools import combinations
def build_combination(n, k):
    result = combinations(range(n), k)
    return result
# --------------------------------------
# 以下为针对模块的测试信息
if __name__ == '__main__':  # 程序被导入,以下代码不执行
    a = build_combination(4, 3)
    for i in a:
        print(i)

2)其他操作

f.readable()  # 文件是否可读
f.writable()  # 文件是否可读
f.closed  # 文件是否关闭
f.encoding  # 如果文件打开模式为b,则没有该属性
f.flush()  # 立刻将文件内容从内存刷到硬盘
f.name

在这里插入图片描述

二、文件模式

1.控制文件读写操作模式

1)r 模式

r只读模式: 在文件不存在时则报错,文件存在文件内指针直接跳到文件开头

 with open('a.txt',mode='r',encoding='utf-8') as f:
     res=f.read() # 会将文件的内容由硬盘全部读入内存,赋值给res
    
小练习:实现用户认证功能 
inp_name=input('请输入你的名字: ').strip()
inp_pwd=input('请输入你的密码: ').strip()
with open(r'db.txt',mode='r',encoding='utf-8') as f:
	for line in f:
#把用户输入的名字与密码与读出内容做比对
		u,p=line.strip('\n').split(':')
	if inp_name == u and inp_pwd == p:
    	print('登录成功')
        break
    else:
        print('账号名或者密码错误')

2)w 模式

w只写模式: 在文件不存在时会创建空文档,文件存在会清空文件,文件指针跑到文件开头

with open('b.txt',mode='w',encoding='utf-8') as f:
    f.write('你好\n')
    f.write('我好\n') 
    f.write('大家好\n')
    f.write('111\n222\n333\n')
#强调:
1 在文件不关闭的情况下,连续的写入,后写的内容一定跟在前写内容的后面
2 如果重新以w模式打开文件,则会清空文件内容

3)a 模式

a只追加写模式: 在文件不存在时会创建空文档,文件存在会将文件指针直接移动到文件末尾

 with open('c.txt',mode='a',encoding='utf-8') as f:
     f.write('44444\n')
     f.write('55555\n')
#强调 w 模式与 a 模式的异同:
1 相同点:在打开的文件不关闭的情况下,连续的写入,新写的内容总会跟在前写的内容之后
2 不同点:以 a 模式重新打开文件,不会清空原文件内容会将文件指针直接移动到文件末尾,新写的内容永远写在最后

小练习:实现注册功能:
 name=input('username>>>: ').strip()
 pwd=input('password>>>: ').strip()
 with open('db1.txt',mode='a',encoding='utf-8') as f:
     info='%s:%s\n' %(name,pwd)
     f.write(info)

4)+ 模式(了解)

r+ w+ a+ :可读可写

#在平时工作中,我们只单纯使用r/w/a,要么只读,要么只写,一般不用可读可写的模式
控制文件读写内容的模式
大前提: tb模式均不能单独使用,必须与r/w/a之一结合使用

# t(默认的):文本模式
# 读写文件都是以字符串为单位的,只能针对文本文件,必须指定encoding参数
# t 模式:如果我们指定的文件打开模式为r/w/a,其实默认就是rt/wt/at
with open('a.txt',mode='rt',encoding='utf-8') as f:
     res=f.read() 
     print(type(res)) # 输出结果为:<class 'str'>
with open('a.txt',mode='wt',encoding='utf-8') as f:
     s='abc'
     f.write(s) # 写入的也必须是字符串类型
#强调:t 模式只能用于操作文本文件,无论读写,都应该以字符串为单位,而存取硬盘本质都是二进制的形式,当指定 t 模式时,内部帮我们做了编码与解码

# b:二进制模式:
# 读写文件都是以bytes/二进制为单位的,可以针对所有文件,一定不能指定encoding参数
# b: 读写都是以二进制位单位
with open('1.mp4',mode='rb') as f:
     data=f.read()
     print(type(data)) # 输出结果为:<class 'bytes'>
with open('a.txt',mode='wb') as f:
     msg="你好"
     res=msg.encode('utf-8') # res为bytes类型
     f.write(res) # 在b模式下写入文件的只能是bytes类型
#强调:b模式对比t模式
1、在操作纯文本文件方面t模式帮我们省去了编码与解码的环节,b模式则需要手动编码与解码,所以此时t模式更为方便
2、针对非文本文件(如图片、视频、音频等)只能使用b模式

小练习: 编写拷贝工具
src_file=input('源文件路径: ').strip()
dst_file=input('目标文件路径: ').strip()
with open(r'%s' %src_file,mode='rb') as read_f,open(r'%s' %dst_file,mode='wb') as write_f:
    for line in read_f:
        # print(line)
        write_f.write(line)

2.主动控制文件内指针移动

#大前提:文件内指针的移动都是Bytes为单位的,唯一例外的是t模式下的read(n),n以字符为单位
with open('a.txt',mode='rt',encoding='utf-8') as f:
     data=f.read(3) # 读取3个字符

with open('a.txt',mode='rb') as f:
     data=f.read(3) # 读取3个Bytes

之前文件内指针的移动都是由读/写操作而被动触发的,若想读取文件某一特定位置的数据,则则需要用f.seek方法主动控制文件内指针的移动,详细用法如下:

f.seek(指针移动的字节数,模式控制):

0: 默认的模式,该模式代表指针移动的字节数是以文件开头为参照的

1: 该模式代表指针移动的字节数是以当前所在的位置为参照的

2: 该模式代表指针移动的字节数是以文件末尾的位置为参照的

强调:其中0模式可以在t或者b模式使用,而1跟2模式只能在b模式下用

1)0模式

a.txt用utf-8编码,内容如下(abc各占1个字节,中文“你好”各占3个字节)

abc你好

0模式的使用

with open('a.txt',mode='rt',encoding='utf-8') as f:
    f.seek(3,0)     # 参照文件开头移动了3个字节
    print(f.tell()) # 查看当前文件指针距离文件开头的位置,输出结果为3
    print(f.read()) # 从第3个字节的位置读到文件末尾,输出结果为:你好
# 注意:由于在t模式下,会将读取的内容自动解码,所以必须保证读取的内容是一个完整中文数据,否则解码失败
with open('a.txt',mode='rb') as f:
    f.seek(6,0)
    print(f.read().decode('utf-8')) #输出结果为: 好

2)1模式

# 1模式的使用
with open('a.txt',mode='rb') as f:
    f.seek(3,1) # 从当前位置往后移动3个字节,而此时的当前位置就是文件开头
    print(f.tell()) # 输出结果为:3
    f.seek(4,1)     # 从当前位置往后移动4个字节,而此时的当前位置为3
    print(f.tell()) # 输出结果为:7

3)2模式

a.txt用utf-8编码,内容如下(abc各占1个字节,中文“你好”各占3个字节)

abc你好

2模式的使用

with open('a.txt',mode='rb') as f:
    f.seek(0,2)     # 参照文件末尾移动0个字节, 即直接跳到文件末尾
    print(f.tell()) # 输出结果为:9
    f.seek(-3,2)     # 参照文件末尾往前移动了3个字节
    print(f.read().decode('utf-8')) # 输出结果为:好
# 小练习:实现动态查看最新一条日志的效果
import time
with open('access.log',mode='rb') as f:
    f.seek(0,2)
    while True:
        line=f.readline()
        if len(line) == 0:
            # 没有内容
            time.sleep(0.5)
        else:
            print(line.decode('utf-8'),end='')

三、文件的修改

# 文件a.txt内容如下
张一蛋     山东    179    49    12344234523
李二蛋     河北    163    57    13913453521
王全蛋     山西    153    62    18651433422
# 执行操作
with open('a.txt',mode='r+t',encoding='utf-8') as f:
    f.seek(9)
    f.write('<妇女主任>')
# 文件修改后的内容如下
张一蛋<妇女主任> 179    49    12344234523
李二蛋     河北    163    57    13913453521
王全蛋     山西    153    62    18651433422
# 强调:
# 1、硬盘空间是无法修改的,硬盘中数据的更新都是用新内容覆盖旧内容
# 2、内存中的数据是可以修改的

文件对应的是硬盘空间,硬盘不能修改对应着文件本质也不能修改。而文件的内容修改是将硬盘中文件内容读入内存,然后在内存中修改完毕后再覆盖回硬盘,具体的实现方式分为两种:

1.文件修改方式一

# 实现思路:将文件内容发一次性全部读入内存,然后在内存中修改完毕后再覆盖写回原文件
# 优点: 在文件修改过程中同一份数据只有一份
# 缺点: 会过多地占用内存
with open('db.txt',mode='rt',encoding='utf-8') as f:
    data=f.read()
with open('db.txt',mode='wt',encoding='utf-8') as f:
    f.write(data.replace('kevin','SB'))

2.文件修改方式二

# 实现思路:以读的方式打开原文件,以写的方式打开一个临时文件,一行行读取原文件内容,修改完后写入临时文件...,删掉原文件,将临时文件重命名原文件名
# 优点: 不会占用过多的内存
# 缺点: 在文件修改过程中同一份数据存了两份
import os
with open('db.txt',mode='rt',encoding='utf-8') as read_f,\
        open('.db.txt.swap',mode='wt',encoding='utf-8') as wrife_f:
    for line in read_f:
        wrife_f.write(line.replace('SB','kevin'))
os.remove('db.txt')
os.rename('.db.txt.swap','db.txt')

3.常见文件头

在这里插入图片描述

四、内置异常类

Python使用异常对象来表示异常状态,并在遇到错误时引发异常。异常对象未被处理(或捕获)时,程序将终止并显示一条错误消息(traceback)。

1.常用异常

类名描述
Exception几乎所有的异常类都是从它派生而来的
AttributeError引用属性或给它赋值失败时引发
OSError操作系统不能执行指定的任务(如打开文件)时引发,有多个子类
IndexError使用序列中不存在的索引时引发,为LookupError的子类
KeyError使用映射中不存在的键时引发,为LookupError的子类
NameError未声明/初始化对象 (没有属性),找不到名称(变量)时引发
SyntaxError代码不正确时引发
TypeError将内置操作或函数用于类型不正确的对象时引发
ValueError将内置操作或函数用于这样的对象时引发:其类型正确但包含的值不合适
ZeroDivisionError在除法或求模运算的第二个参数为零时引发

2.其他异常

1)Exit

类名描述
SystemExit解释器请求退出
GeneratorExit生成器(generator)发生异常来通知退出

2)Error

类名描述
StandardError所有的内建标准异常的基类
FloatingPointError浮点计算错误
OverflowError数值运算超出最大限制
AssertionError断言语句失败
EOFError没有内建输入,到达EOF 标记
EnvironmentError操作系统错误的基类
IOError输入/输出操作失败
WindowsError系统调用失败
ImportError导入模块/对象失败
LookupError无效数据查询的基类
MemoryError内存溢出错误(对于Python 解释器不是致命的)
UnboundLocalError访问未初始化的本地变量
ReferenceError弱引用(Weak reference)试图访问已经垃圾回收了的对象
RuntimeError一般的运行时错误
NotImplementedError尚未实现的方法
IndentationError缩进错误
TabErrorTab 和空格混用
SystemError一般的解释器系统错误

3)Unicode

类名描述
UnicodeErrorUnicode 相关的错误
UnicodeDecodeErrorUnicode 解码时的错误
UnicodeEncodeErrorUnicode 编码时错误
UnicodeTranslateErrorUnicode 转换时错误

4)Warning

类名描述
Warning警告的基类
DeprecationWarning关于被弃用的特征的警告
FutureWarning关于构造将来语义会有改变的警告
OverflowWarning旧的关于自动提升为长整型(long)的警告
PendingDeprecationWarning关于特性将会被废弃的警告
RuntimeWarning可疑的运行时行为(runtime behavior)的警告
SyntaxWarning可疑的语法的警告
UserWarning用户代码生成的警告

5)其他

类名描述
BaseException所有异常的基类
KeyboardInterrupt用户中断执行(通常是输入^C)
StopIteration迭代器没有更多的值

五、捕获异常

  • try…except…检测一段代码内出现的异常并将其归类后的相关信息输出。
  • 把可能发生错误的语句放在try模块里,用except来处理异常。
  • except可以处理一个专门的异常,也可以处理一组圆括号中的异常,
  • 如果except后没有指定异常,则默认处理所有异常。每一个try都必须至少有一个except
try: 
	被检测代码段  
except Exception[as reason]: 
	相关信息

在except Exception 加上as reason将程序检测到的出错的信息输出

# 例一
try:
    f = open('该文档不存在')
    print(f.read())
    f.close()
except OSError as reason:
    print('文件出错了T_T')
    print('出错原因是' + str(reason))


# 文件出错了T_T
# 出错原因是[Errno 2] No such file or directory: '该文档不存在'

# 例二
try:
    fishc
    1 + '1'
    f = open('该文档不存在')
    print(f.read())
    f.close()
except (OSError,TypeError,NameError) as reason:
    print('出错了T_T')
    print('出错原因是' + str(reason))
    
# 出错了T_T
# 出错原因是name 'fishc' is not defined
# 举例说明:
try:
    f = open('该文档不存在')
    print(f.read())
    f.close()
except OSError:
    print('文件出错了T_T')
    
# 文件出错了T_T

异常从函数向外传播到调用函数的地方。如果在这里也没有被捕获,异常将向程序的最顶层传播。这意味可使用try/except来捕获他人所编写函数引发的异常。

1.格式

1)万能异常

Exception可接受任何异常。Exception 没有指定错误类型,捕获的异常过于宽泛,没有针对性

s1 = 'hello'
try:
    int(s1)
except Exception as e:
    print(e)
    
# invalid literal for int() with base 10: 'hello'  (基数为10的int()字面量无效:'hello')

关闭编译器中代码检测中有关检测 Exception 的选项,在 try 语句前加入# noinspection PyBroadException

# noinspection PyBroadException

try:
  pass
except Exception as e:
  pass

2)指定异常

指定异常类只能来处理指定的异常情况,如果非指定异常则无法处理

s1 = 'hello'
try:
    int(s1)
except IndexError as e: # 未捕获到异常,程序直接报错
    print (e)

except语句无Except,则程序检测到异常就会执行except下的语句,但不推荐使用:

try:
    fishc
    1 + '1'
    f = open('该文档不存在')
    print(f.read())
    f.close()
except:
    print('出错了T_T')

# 出错了T_T

3)多分支异常

增加多个except语句,提取代码段不同的异常问题

s1 = 'hello'
try:
    int(s1)
except IndexError as e:
    print(e)
except KeyError as e:
    print(e)
except ValueError as e:
    print(e)

# invalid literal for int() with base 10: 'hello'  (基数为10的int()字面量无效:'hello')

若使用一个except子句捕获多种异常,可在一个元组中指定这些异常,括号不得省略

s1 = 'hello'
try:
    int(s1)
except (IndexError, KeyError, ValueError) as e:
    print(e)
# invalid literal for int() with base 10: 'hello'

程序检测到第一个异常后即停止运行,在except中找到相应输出语句,若except未包含时则直接曝出异常

try:
    fishc
    1 + '1'
    f = open('该文档不存在')
    print(f.read())
    f.close()
except OSError as reason:
    print('文件出错了T_T')
    print('出错原因是%s'%str(reason))
except TypeError as reason:
    print('求和出错了T_T')
    print('出错原因是%s'%str(reason))

# Traceback (most recent call last):
#   File "D:/Python34/test/033/01.py", line 2, in <module>
#     fishc
# NameError: name 'fishc' is not defined

4)else

else在捕获异常语句中没有出现异常时执行,可用于执行捕获异常语句的其他功能,也可用作跳出语句的手段

while True:
    try:
        x = int(input('Enter the first number: '))
        y = int(input('Enter the second number:'))
        value = x / y
        print('x / y is', value)
    except Exception as e:
        print('Invalid input:', e)
        print('Please try again')
    else:
        break

5)finally

finally语句无论是否发生异常都将会执行最后的代码。在try…except语句下面补充,用于程序检测到异常后仍能执行的语句:

try:
	<语句>
finally:
	<语句>    #退出try时总会执行

finally子句用于确保文件或网络套接字等得以关闭

s1 = 'hello'
try:
    int(s1)
except (IndexError, KeyError, ValueError, Exception) as e:
    print(e)
else:
    print('try内代码块没有异常则执行我')
finally:
    print('无论异常与否,都会执行该模块,通常是进行清理工作')
    
# invalid literal for int() with base 10: 'hello'
# 无论异常与否,都会执行该模块,通常是进行清理工作

2.raise语句

  • 要引发异常可使用raise语句,并将一个类(必须是Exception的子类)或实例作为参数。将类作为参数时,将自动创建一个实例。
  • raise 语句引发的异常通常用 try except(else finally)异常处理结构来捕获并进行处理。
  • 虽然程序中使用了 raise 语句引发异常,但程序的执行是正常的,手动抛出的异常并不会导致程序崩溃。
# 引发当前上下文中捕获的异常(比如在 except 块中),或默认引发 RuntimeError 异常。
raise

# raise 后带一个异常类名称,表示引发执行类型的异常。
raise [exceptionName]

# 在引发指定类型的异常的同时,附带异常的描述信息。
raise [exceptionName [(reason)]]
  • [ ]中为可选参数,指定抛出的异常名称,以及异常信息的相关描述。
  • 如果可选参数全部省略,则 raise 会把当前错误原样抛出;
  • 如果仅省略 (reason),则在抛出异常时,将不附带任何的异常描述信息。
  • 每次执行 raise 语句,都只能引发一次执行的异常。
# 在没有引发过异常的程序使用无参raise默认引发RuntimeError异常
try:
    a = input("输入一个数:")
    if not a.isdigit():
        raise
except RuntimeError as e:
    print("引发异常:",repr(e))
# 输入一个数:a
# 引发异常: RuntimeError('No active exception to reraise')


raise Exception
# Traceback (most recent call last):
#   File "F:\PythonProject\TEXT\text2.py", line 1, in <module>
#     raise Exception
# Exception

raise Exception('hyperdrive overload')
# Traceback (most recent call last):
#   File "F:\PythonProject\TEXT\text2.py", line 1, in <module>
#     raise Exception('hyperdrive overload')
# Exception: hyperdrive overload

1)抑制异常

捕获异常后重新引发它(即继续向上传播)可调用raise且不提供任何参数。

如下为“抑制”异常ZeroDivisionError的计算器类。启用此功能计算器将打印一条错误消息而不让异常继续传播。在与用户交互的会话中抑制异常;在程序内部使用时关闭“抑制”功能:

class MuffledCalculator:
    muffled = False

    # 发生除零行为启用“抑制”功能方法calc将(隐式地)返回None。
    def calc(self, expr):
        try:
            return eval(expr)
        except ZeroDivisionError:
            if self.muffled:
                print('Division by zero is illegal')
            else:
                raise


calculator = MuffledCalculator()

print(calculator.calc('10 / 2'))
# 5.0
# 默认关闭抑制功能报错结束程序
calculator.calc('10 / 0')
# Traceback (most recent call last): File "<stdin>", line 1, in ? 
#  File "MuffledCalculator.py", line 6, in calc 
#    return eval(expr) 
#  File "<string>", line 0, in ?

# 开启抑制功能
calculator.muffled = True
print(calculator.calc('10 / 0'))
# Division by zero is illegal

2)引发异常

引发异常的情况下进入except子句的异常将被作为异常上下文存储起来,并出现在最终的错误消息中

try:
    1 / 0
except ZeroDivisionError:
    raise ValueError

# Traceback (most recent call last): 
#   File "<stdin>", line 2, in <module> 
# ZeroDivisionError: division by zero 
# 
# 在处理上述异常时,引发了另一个异常:
# 
# Traceback (most recent call last): 
#   File "<stdin
class ShortInputException(Exception):
    '''自定义的异常类'''
    def __init__(self, length, atleast):
        #super().__init__()
        self.length = length
        self.atleast = atleast

def main():
    try:
        s = input('请输入 --> ')
        if len(s) < 3:
            # raise引发一个你定义的异常
            raise ShortInputException(len(s), 3)
    except ShortInputException as result:#x这个变量被绑定到了错误的实例
        print('ShortInputException: 输入的长度是 %d,长度至少应是 %d'% (result.length, result.atleast))
    else:
        print('没有异常发生.')

main()

# 请输入 --> 12
# ShortInputException: 输入的长度是 2,长度至少应是 3

3)raise…from…

可用raise … from …语句来提供自己的异常上下文,也可使用None来禁用上下文。

try:
    1 / 0
except ZeroDivisionError:
    raise ValueError from None

# Traceback (most recent call last): 
#   File "<stdin>", line 4, in <module> 
# ValueError

3.assert断言

assert断言用于使完善程序前出现错误就结束程序,是声明其布尔值必须为真的判定。发生异常说明表达式为假,返回值为假,从而触发异常。

# 当 assert 语句失败的时候,会引发AssertionError。
assert 表达式

# 为assert断言语句添加异常参数,assert的异常参数是在断言表达式后添加字符串信息,用来解释断言并更好的知道是哪里出了问题。
assert expression [, arguments]
# assert 表达式 [, 参数]
class ShortInputException(Exception):
    '''自定义的异常类'''
    def __init__(self, length, atleast):
        #super().__init__()
        self.length = length
        self.atleast = atleast

def main():
    try:
        s = input('请输入 --> ')
        assert len(s) >= 3, ShortInputException(len(s), 3)
    except  AssertionError as err:
        detail = err.args[0]
        print('ShortInputException: 输入的长度是 %d,长度至少应是 %d'% (detail.length, detail.atleast))
    else:
        print('没有异常发生.')

main()

# 请输入 --> er
# ShortInputException: 输入的长度是 2,长度至少应是 3

4.自定义异常类

创建异常类时,异常应通过直接或间接的方式继承Exception类。

例与BaseException相关的实例中创建了一个类,基类为BaseException,用于在异常触发时输出更多的信息。在try语句块中,用户自定义的异常后执行except块语句,变量 e 用于创建Networkerror类的实例。

class Networkerror(BaseException):
    def __init__(self,msg):
        self.msg=msg
    def __str__(self):
        return self.msg

try:
    raise Networkerror('类型错误')
except Networkerror as e:
    print(e)

# 类型错误

5.注意事项

1)异常与函数

不处理函数中引发的异常,它将向上传播到调用函数的地方。如果在那里也未得到处理异常将继续传播直至到达主程序(全局作用域)。如果主程序中也没有异常处理程序,程序将终止并显示栈跟踪消

def faulty():
    raise Exception('Something is wrong')


def ignore_exception():
    faulty()


def handle_exception():
    try:
        faulty()
    except:  # 这样写会使异常子句过于宽泛
        print('Exception handled')

# faulty中引发的异常依次从faulty和ignore_exception向外传播,最终导致显示一条栈跟踪消息。调用handle_exception时,异常最终传播到handle_exception,并被这里的try/except语句处理
ignore_exception()
# Traceback (most recent call last):
#   File "F:\PythonProject\TEXT\text2.py", line 16, in <module>
#     ignore_exception()
#   File "F:\PythonProject\TEXT\text2.py", line 6, in ignore_exception
#     faulty()
#   File "F:\PythonProject\TEXT\text2.py", line 2, in faulty
#     raise Exception('Something is wrong')
# Exception: Something is wrong

2)异常与条件语句

  • 不希望出现代码可能引发某种异常时程序终止并显示栈跟踪消息,可添加必要的try/except或try/finally语句(或结合使用)来处理。
  • 可使用条件语句来实现异常处理,但这样的代码可能不自然,可读性也没那么高。
  • 在很多情况下相比于使用if/else,使用try/except语句更自然,也更符合Python的风格。因此你应养成尽可能使用try/except语句的习惯

设一个字典,在指定键存在时打印与之相关联的值,否则什么都不做。

def describe_person(person):
    print('Description of', person['name'])
    print('Age:', person['age'])
    if 'occupation' in person:
        print('Occupation:', person['occupation'])


describe_person({'name': "Throatwobbler Mangrove", 'age': 42})
# Description of Throatwobbler Mangrove
# Age: 42
describe_person({'name': "Throatwobbler Mangrove", 'age': 42, 'occupation': "camper"})
# Description of Throatwobbler Mangrove
# Age: 42
# Occupation: camper

代码必须两次查找’occupation’键:一次检查这个键是否存在(在条件中),另一次获取这个键关联的值,以便将其打印出来。下面是另一种解决方案:

def describe_person(person):
    print('Description of', person['name'])
    print('Age:', person['age'])
    # 假设存在'occupation'键则直接获取并打印值,而无需检查这个键是否存在。如果这个键不存在,将引发KeyError异常,而except子句将捕获这个异常
    try:
        print('Occupation:', person['occupation'])
    except KeyError:
        pass


describe_person({'name': "Throatwobbler Mangrove", 'age': 42})
# Description of Throatwobbler Mangrove
# Age: 42
describe_person({'name': "Throatwobbler Mangrove", 'age': 42, 'occupation': "camper"})
# Description of Throatwobbler Mangrove
# Age: 42
# Occupation: camper

检查对象是否包含特定的属性时,try/except也很有用。如检查一个对象是否包含属性write

# try只访问属性write而不使用。若引发AttributeError异常说明对象没有属性write,否则就说明有这个属性。
try:
    obj.write
except AttributeError:
    print('The object is not writeable')
else:
    print('The object is writeable')

2)异常警告

以下函数均属于模块warnings

(1)warn

只发出警告指出情况偏离了正轨,可使用模块warnings中的函数warn。警告只显示一次。如果再次运行最后一行代码,什么事情都不会发生。

from warnings import warn

warn("I've got a bad feeling about this.")
# text2.py:2: UserWarning: I've got a bad feeling about this.
# warn("I've got a bad feeling about this.")
(2)filterwarnings

如果其他代码在使用含warn的模块,可使用模块warnings中的函数filterwarnings来抑制发出的警告(或特定类型的警告),并指定要采取的措施,如"error"或"ignore“

from warnings import filterwarnings, warn

# 引发的异常为UserWarning。
filterwarnings("ignore")
warn("Anyone out there?")
filterwarnings("error")
warn("Something is very vwrong!")

# Traceback (most recent call last):
#   File "F:\PythonProject\TEXT\text2.py", line 6, in <module>
#     warn("Something is very vwrong!")
# UserWarning: Something is very vwrong!

发出警告时可指定将引发的异常(即警告类别),但必须是Warning的子类。如果将警告转换为错误将使用指定的异常。还可根据异常来过滤掉特定类型的警告。

from warnings import filterwarnings, warn

filterwarnings("error")
warn("This function is really old...", DeprecationWarning)
# Traceback (most recent call last):
#   File "F:\PythonProject\TEXT\text2.py", line 4, in <module>
#     warn("This function is really old...", DeprecationWarning)
# DeprecationWarning: This function is really old...
filterwarnings("ignore", category=DeprecationWarning)
warn("Another deprecation warning.", DeprecationWarning)
warn("Something else.")
# F:\PythonProject\TEXT\text2.py:11: UserWarning: Something else.
#   warn("Something else.")

六、浅拷贝与深拷贝

1.浅拷贝

对data的两种拷贝方式:

  • 普通赋值:data_mycopy = data
  # 常用拷贝
  x = [1, 2, 3]
  y = x
  print(x, y)
  print(id(x), id(y))
  x.append(0)
  print(x, y)
  print(id(x), id(y))
  
  # [1, 2, 3] [1, 2, 3]
  # 1836179465280 1836179465280
  # [1, 2, 3, 0] [1, 2, 3, 0]
  # 1836179465280 1836179465280
  
  # x,y内存地址一致,x改变y随之改变。常规拷贝在原始变量x的改变后,因为共用同一个内存地址,因此会直接放到被复制的变量y上,导致“不知情”的情况下导致y变量在没有操作的情况下改变。
  • 浅拷贝:data_copy = copy.copy(da1ta)

    # 浅拷贝
    x = [1, 2, 3]
    y = x.copy()
    print(x, y)
    print(id(x), id(y))
    x.append(0)
    print(x, y)
    print(id(x), id(y))
    
    # [1, 2, 3] [1, 2, 3]
    # 2686580499520 2686586295680
    # [1, 2, 3, 0] [1, 2, 3]
    # 2686580499520 2686586295680
    
    # 浅拷贝会将两个变量分别放在不同的内存地址,解决了常规拷贝的缺点。
    

1)可变类型

浅拷贝只对可变对象的第一层对象进行拷贝,对拷贝对象开辟新的内存空间进行存储,而不会拷贝内部的子对象。拷贝可变类型,拷贝对象发生改变,被拷贝对象和拷贝出的对象内存地址都不变。如上例。

2)不可变类型

不可变类型浅拷贝使用“=”拷贝,不会给拷贝对象开辟新内存空间,只拷贝这个对象的引用。浅拷贝拷贝不可变类型,拷贝对象(val1)不变,则拷贝出的对象(val2)和被拷贝对象(val1)指向用一块内存;拷贝对象发生改变,则拷贝对象的内存则改变,但拷贝出来的对象的内存地址不变。

x = "冯宝宝"
y = x
print(x, y)
print(id(x), id(y))
x = "张伯仲"
print(x, y)
print(id(x), id(y))

# 冯宝宝 冯宝宝
# 2574485449264 2574485449264
# 张伯仲 冯宝宝
# 2574485452432 2574485449264

2.深拷贝

深拷贝使用的是deepcopy函数。浅拷贝只拷贝原对象在内存中引用地址,让新对象指向这个地址。而深拷贝遍历拷贝这个对象的所有内容,与原来没关系。如果此时修改原对象的值,新对象不会随之更改。

from copy import deepcopy

a1 = deepcopy(a2)

1)可变类型

对于字典或列表中有嵌套的情况,浅拷贝不会生效,故需要深拷贝。深拷贝可变类型,只要发现拷贝对象中有可变类型就会对该对象到最后一个可变类型的每一层进行逐层拷贝,对每一层对象都会开辟新的内存空间进行存储。

# 不会生效的浅拷贝
feng_chen_fei = {"contact": {"age": 18, "high": 160, "weight": 50}}
zhang_bo_yang = feng_chen_fei.copy()
print(id(feng_chen_fei), id(zhang_bo_yang))
feng_chen_fei["contact"]["age"] = 19
print(id(feng_chen_fei), id(zhang_bo_yang))
for i in feng_chen_fei:
    print(i + ":" + str(feng_chen_fei[i]))
for i in zhang_bo_yang:
    print(i + ":" + str(zhang_bo_yang[i]))
    
# 2215047104512 2215047105472
# 2215047104512 2215047105472
# contact:{'age': 19, 'high': 160, 'weight': 50}
# contact:{'age': 19, 'high': 160, 'weight': 50}
from copy import deepcopy
# 生效的深拷贝
feng_chen_fei = {"contact": {"age": 18, "high": 160, "weight": 50}}
zhang_bo_yang = deepcopy(feng_chen_fei)
print(id(feng_chen_fei), id(zhang_bo_yang))
feng_chen_fei["contact"]["age"] = 19
print(id(feng_chen_fei), id(zhang_bo_yang))
for i in feng_chen_fei:
    print(i + ":" + str(feng_chen_fei[i]))
for i in zhang_bo_yang:
    print(i + ":" + str(zhang_bo_yang[i]))
    
# 1787235606592 1787241713472
# 1787235606592 1787241713472
# contact:{'age': 19, 'high': 160, 'weight': 50}
# contact:{'age': 18, 'high': 160, 'weight': 50}

2)不可变类型

说明:不可变类型深拷贝不会给拷贝对象开辟新的空间进行存储,而只是拷贝了这个对象的引用

import copy

val1 = 1000
val2 = copy.deepcopy(val1)
print(id(val1), id(val2))
val1 += 1
print(val1, val2)
print(id(val1), id(val2))

# 2566924713520 2566924713520
# 1001 1000
# 2566929912688 2566924713520

3.总结

  • 浅拷贝使用的是copy.copy函数,深拷贝使用的是copy.deepcopy函数
  • 可变类型不管是对对象进行深拷贝还是浅拷贝,只要拷贝成功就会开辟新的内存空间进行存储拷贝对象、
  • 浅拷贝和深拷贝的区别是:浅拷贝最多只拷贝对象的第一层,深拷贝可能拷贝对象的多层,并且保证了数据的独立性和安全性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木颤简叶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值