python实践笔记(三): 异常处理和文件操作

news2024/12/23 14:19:24

1. 写在前面

最近在重构之前的后端代码,借着这个机会又重新补充了关于python的一些知识, 学习到了一些高效编写代码的方法和心得,比如构建大项目来讲,要明确捕捉异常机制的重要性, 学会使用try...except..finally, 要通过日志模块logging监控整个系统的性能,学会把日志输出到文件并了解日志层级,日志模块的一些工作原理,能自定义日志,这样能非常方便的debug服务,学会使用装饰器对关键接口进行时间耗时统计,日志打印等装饰,减少代码的重复性, 学会使用类的类方法,静态方法对一些公用函数进行封装,来增强代码的可维护性, 学会使用文档对函数和参数做注解, 学会函数的可变参数统一代码的风格等等, 这样能使得代码从可读性,可维护性, 灵活性和执行效率上都有一定的提升,写出来的代码也更加优美一些。 所以把这几天的学习,分模块整理几篇笔记, 这次是从实践的再去补充python的内容,目的是要写出漂亮的python代码,增强代码的可读,可维护,灵活和高效,方便调试和监控

这篇文章介绍两块内容,分别是异常处理机制以及文件操作, 异常处理操作在大型下项目里面非常有用,开发人员在编写程序时,难免会遇到错误,有的是编写人员疏忽造成的语法错误,有的是程序内部隐含逻辑问题造成的数据错误,还有的是程序运行时与系统的规则冲突造成的系统错误等等,通过异常处理,可以全局掌控自己的程序。 文件操作也是非常实用的一个技术,把文件随时存储到文件进行持久化,是一个非常好的习惯。

大纲如下:

  • 异常处理机制
  • 文件操作

Ok, let’s go!

2. 异常处理机制

2.1 What?

开发人员在编写程序时,难免会遇到错误,有的是编写人员疏忽造成的语法错误,有的是程序内部隐含逻辑问题造成的数据错误,还有的是程序运行时与系统的规则冲突造成的系统错误,等等。

  • 语法错误: 写的代码不符合python语法规则, 解析器帮我们检查,如果发现及时报错, 这个解析器零容忍, 修复之后才能说运行的事情
  • 运行错误:语法上没有问题能过,但运行的时候发生错误,比如1/0这种。

在 Python 中,把这种运行时产生错误的情况叫做异常(Exceptions)

常见的异常如下:

异常类型解释demo
AssertionError当 assert 关键字后的条件为假时,程序运行会停止并抛出 AssertionError 异常def add(a: int, b: int) -> int:
assert isinstance(a, int) and isinstance(b, int),
f"{a} or {b} is invalid!, please check"<br.return a + b
add(3.5, 3) # AssertionError: 3.5 or 3 is invalid!, please check
AttributeError当试图访问的对象属性不存在时抛出的异常a = {”name”: “zhongqiang”}
print(a.len) # dict 没有属性len
IndexError索引超出序列范围会引发此异常a = [1, 2, 3]
print(a[4]) # IndexError: list index out of range
KeyError字典中查找一个不存在的关键字时引发此异常a = {”name”: “zhongqiang”}
print(a[’age’]) # KeyError
NameError尝试访问一个未声明的变量时,引发此异常print(hello)
TypeError不同类型数据之间的无效操作1 + “hello”
ZeroDivisionError除法运算中除数为 0 引发此异常1 / 0

开发者可以使用异常处理全面地控制自己的程序。异常处理不仅仅能够管理正常的流程运行,还能够在程序出错时对程序进行必的处理。大大提高了程序的健壮性和人机交互的友好性。

2.2 Why?

程序员的素养之一,就是写代码的时候尽可能考虑更多的边界, 想到更多的异常,并对可能会发生的异常做对应处理。从而保证自己代码的健壮性。

使用 Python 异常处理机制,可以让程序中的异常处理代码和正常业务代码分离,使得程序代码更加优雅,并可以提高程序的健壮性。

2.3 How?

Python 中,用try except语句块捕获并处理异常,其基本语法结构如下所示:

try:
    可能产生异常的代码块
except [ (Error1, Error2, ... ) [as e] ]:
    处理异常的代码块1
except [ (Error3, Error4, ... ) [as e] ]:  # 一个Except可以处理多个异常
    处理异常的代码块2
except  [Exception]:  # 作为可选参数,可以代指程序可能发生的所有异常情况,其通常用在最后一个 except 块
    处理其它异常

# 执行过程:
	# 1. 首先执行 try 中的代码块,如果执行过程中出现异常,系统会自动生成一个异常类型,并将该异常提交给 Python 解释器,此过程称为捕获异常
	# 2. 当 Python 解释器收到异常对象时,会寻找能处理该异常对象的 except 块,如果找到合适的 except 块,则把该异常对象交给该 except 块处理,这个过程被称为处理异常

try:
    a = int(input("输入被除数:"))
    b = int(input("输入除数:"))
    c = a / b
    print("您输入的两个数相除的结果是:", c )
except (ValueError, ArithmeticError):
    print("程序发生了数字格式异常、算术异常之一")
except :
    print("未知异常")
print("程序继续运行")

# 获取特定异常的有关信息
# 每种异常类型都提供了如下几个属性和方法,通过调用它们,就可以获取当前处理异常类型的相关信息:
	# args:返回异常的错误编号和描述字符串;
	# str(e):返回异常信息,但不包括异常信息的类型;
	# repr(e):返回较全的异常信息,包括异常信息的类型。
try:
    1/0
except Exception as e:
    # 访问异常的错误编号和详细信息
    print(e.args)  # ('division by zero',)
    print(str(e))   # division by zero
    print(repr(e))   # ZeroDivisionError('division by zero',)

# 关于异常的更多信息,可以使用traceback 模块  该模块提供了一个标准接口来提取、格式化和打印 Python 程序的栈跟踪结果。
# 1. 直接打印异常
import traceback
try:
    raise SyntaxError, "traceback test"
except:
    traceback.print_exc()
# 2. 错误输出到日志
logger.info(traceback.format_exc())

# try-except-else结构
# 使用 else 包裹的代码,只有当 try 块没有捕获到任何异常时,才会得到执行;反之,如果 try 块捕获到异常,即便调用对应的 except 处理完异常,else 块中的代码也不会得到执行。
try:
    result = 20 / int(input('请输入除数:'))
    print(result)
except ValueError:
    print('必须输入整数')
except ArithmeticError:
    print('算术错误,除数不能为 0')
else:
    print('没有出现异常')
    result = result * 2
		print("继续执行")
		print(result)

# try-except-finally: 资源回收
# Python 异常处理机制还提供了一个 finally 语句,通常用来为 try 块中的程序做扫尾清理工作。
# 注意,和 else 语句不同,finally 只要求和 try 搭配使用,而至于该结构中是否包含 except 以及 else,对于 finally 不是必须的(else 必须和 try except 搭配使用)
# finally 语句的功能是:无论 try 块是否发生异常,最终都要进入 finally 语句,并执行其中的代码块。
# 当 try 块中的程序打开了一些物理资源(文件、数据库连接等)时,由于这些资源必须手动回收,而回收工作通常就放在 finally 块中。
# Python 垃圾回收机制,只能帮我们回收变量、类对象占用的内存,而无法自动完成类似关闭文件、数据库连接等这些的工作
try:
    #发生异常
    print(20/0)
[except xxx]   # 可选
finally :   # 肯定会执行
    print("执行 finally 块中的代码")

# 所以最终的异常处理语法结构
try:
    #业务实现代码
except Exception1 as e:
    #异常处理块1
    ...
except Exception2 as e:
    #异常处理块2
    ...
#可以有多个 except
...
else:
    #正常处理块
finally :
    #资源回收块
    ...

# 使用注意
	# 在整个异常处理结构中,只有 try 块是必需的,也就是说:
	# except 块、else 块、finally 块都是可选的,当然也可以同时出现;
	# 可以有多个 except 块,但捕获父类异常的 except 块应该位于捕获子类异常的 except 块的后面;
	# 多个 except 块必须位于 try 块之后,finally 块必须位于所有的 except 块之后。
	# 要使用 else 块,其前面必须包含 try 和 except

# else 语句块只有在没有异常发生的情况下才会执行
# finally 语句则不管异常是否发生都会执行。不仅如此,无论是正常退出、遇到异常退出,还是通过 break、continue、return 语句退出,finally 语句块都会执行
# 下面这个情况除外
try:
    os._exit(1)  # 这个退出python解释器了
finally:
    print("执行finally语句")

# 另外在通常情况下,不要在 finally 块中使用如 return 或 raise 等导致方法中止的语句(raise 语句将在后面介绍)
# 一旦在 finally 块中使用了 return 或 raise 语句,将会导致 try 块、except 块中的 return、raise 语句失效
def test():
    try:
        # 因为finally块中包含了return语句
        # 所以下面的return语句失去作用
        return True
    finally:
        return False
print(test())

# 如果Python程序在执行try块、except块包含有return或raise语句,则Python解释器执行到该语句时,会先去查找finally块
# 如果没有finally块,程序才会立即执行return或 raise语句;反之,如果找到 finally 块,系统立即开始执行 finally 块
# 只有当 finally 块执行完成后,系统才会再次跳回来执行 try 块、except 块里的 return 或 raise 语句。
# 但是,如果在 finally 块里也使用了 return 或 raise 等导致方法中止的语句,finally 块己经中止了方法,系统将不会跳回去执行 try 块、except 块里的任何代码。

except匹配原理:

在这里插入图片描述

当 try 块捕获到异常对象后,Python 解释器会拿这个异常类型依次和各个 except 块指定的异常类进行比较,如果捕获到的这个异常类,和某个 except 块后的异常类一样,又或者是该异常类的子类,那么 Python 解释器就会调用这个 except 块来处理异常;反之,Python 解释器会继续比较,直到和最后一个 except 比较完,如果没有比对成功,则证明该异常无法处理。

注意, python的异常机制方便也能使代码健壮,但使用也得注意几点:

  1. 不要过度使用异常

    1. 把异常和普通错误混淆在一起,不再编写任何错误处理代码,而是以简单地引发异常来代苦所有的错误处理。
    2. 使用异常处理来代替流程控制
  2. 不要使用庞大的try块

  3. 不要忽略捕捉到的异常

    既然己捕获到异常,那么 except 块理应做些有用的事情,及处理并修复异常。except 块整个为空,或者仅仅打印简单的异常信息都是不妥的!

    except 块为空就是假装不知道甚至瞒天过海,这是最可怕的事情,程序出了错误,所有人都看不到任何异常,但整个应用可能已经彻底坏了。仅在 except 块里打印异常传播信息稍微好一点,但仅仅比空白多了几行异常信息。通常建议对异常采取适当措施,比如:

    • 处理异常。对异常进行合适的修复,然后绕过异常发生的地方继续运行;或者用别的数据进行计算,以代替期望的方法返回值;或者提示用户重新操作……总之,程序应该尽量修复异常,使程序能恢复运行。
    • 重新引发新异常。把在当前运行环境下能做的事情尽量做完,然后进行异常转译,把异常包装成当前层的异常,重新传给上层调用者。
    • 在合适的层处理异常。如果当前层不清楚如何处理异常,就不要在当前层使用 except 语句来捕获该异常,让上层调用者来负责处理该异常。

2.4 raise用法

Python允许我们在程序中手动设置异常,使用 raise 语句即可。有些异常,是程序错误导致的运行异常,这种解释器会帮助我们抛出来,然后我们捕捉到进行处理,但还有些异常,是程序正常运行的结果,程序能正常运行,但是结果上可能不是我们想要的,这种情况我们也要考虑在内,然后用raise手动抛出, 再捕捉进行处理。

# 语法:raise [exceptionName [(reason)]], 常用方法
	# 1. raise:单独一个 raise。该语句引发当前上下文中捕获的异常(比如在 except 块中),或默认引发 RuntimeError 异常。
	# 2. raise 异常类名称:raise 后带一个异常类名称,表示引发执行类型的异常。
	# 2. raise 异常类名称(描述信息):在引发指定类型的异常的同时,附带异常的描述信息

try:
    a = input("输入一个数:")
    #判断用户输入的是否为数字
    if(not a.isdigit()):
		    # raise
		    # raise ValueError
        raise ValueError("a 必须是数字")
except ValueError as e:
    print("引发异常:",repr(e))

# assert断言也能起到调试的功效
try:
    s_age = input("请输入您的年龄:")
    age = int(s_age)
    assert 20 < age < 80 , "年龄不在 20-80 之间"
    print("您输入的年龄在20和80之间")
except AssertionError as e:
    print("输入年龄不正确",e)

2.5 自定义异常

实际开发中,有时候系统提供的异常类型不能满足开发的需求。这时就可以创建一个新的异常类来拥有自己的异常。

class InputError(Exception):
    '''当输出有误时,抛出此异常'''
    #自定义异常类型的初始化
    def __init__(self, value):
        self.value = value
    # 返回异常类对象的说明信息
    def __str__(self):
        return ("{} is invalid input".format(repr(self.value)))
   
try:
    raise InputError(1) # 抛出 MyInputError 这个异常
except InputError as err:
    print('error: {}'.format(err))

需要注意的是,自定义一个异常类,通常应继承自 Exception 类(直接继承),当然也可以继承自那些本身就是从 Exception 继承而来的类(间接继承 Exception),命名上尽量以Error结尾,符合python的规范。

在这里插入图片描述

3. 文件操作

3.1 绝对路径和相对路径

在文件操作里面,首先得写对文件存在的路径,否则会报错找不到文件,所以这里先介绍几个常用的函数, 查看当前工作目录,以及修改工作目录,以及获取文件的绝对路径和相对路径来解决这个问题。

 os.getcwd(): 获得当前工作路径的字符串
 os.chdir("xxx"): 改变当前工作目录
 
# 获取到工作目录之后, 再查看文件的绝对和相对路径
# 绝对路径: 从根目录开始到当前文件
# 相对路径: 从当前工作目录开始到当前文件
os.path.abspath(path) 将返回 path 参数的绝对路径的字符串,这是将相对路径转换为绝对路径的简便方法。
os.path.isabs(path),如果参数是一个绝对路径,就返回 True,如果参数是一个相对路径,就返回 False。
os.path.relpath(path, start) 将返回从 start 路径到 path 的相对路径的字符串。如果没有提供 start,就使用当前工作目录作为开始路径。
os.path.dirname(path) 将返回一个字符串,它包含 path 参数中最后一个斜杠之前的所有内容;
os.path.basename(path) 将返回一个字符串,它包含 path 参数中最后一个斜杠之后的所有内容

# 如果同时需要一个路径的目录名称和基本名称,就可以调用 os.path.split() 获得这两个字符串的元组, 这个内部就是按照最后一个/分开的, 自动获取dirname以及basename

# 如果提供的路径不存在,许多 Python 函数就会崩溃并报错,但好在 os.path 模块提供了以下函数用于检测给定的路径是否存在,以及它是文件还是文件夹
	# 1. 如果 path 参数所指的文件或文件夹存在,调用 os.path.exists(path) 将返回 True,否则返回 False。
	# 2. 如果 path 参数存在,并且是一个文件,调用 os.path.isfile(path) 将返回 True,否则返回 False。
	# 3. 如果 path 参数存在,并且是一个文件夹,调用 os.path.isdir(path) 将返回 True,否则返回 False。

3.2 文件操作的基本步骤

这里说的文件操作主要是应用级别的操作, 也就是向文件里面写内容。 主要分为三步:

  1. 打开文件:使用 open() 函数,该函数会返回一个文件对象;

    file = open(file_name [, mode='r' [ , buffering=-1 [ , encoding = None ]]])
    print(file)  # 文件对象
    
    # file_name: 文件名, 路径要写对
    # mode: 可选参数,用于指定文件的打开模式。 读或写, 常用的如下:
    	# 读: r(只读, 文件必须存在)、rb(二进制哥格式读,一般用于图片,视频, 文件必须存在)
    	# 写:  w,wb(写内容,文件不在则创建,会清空原来内容), a ab(追加模式打开文件,不会清除已有内容,文件不存在则创建)
    # buffering:可选参数,用于指定对文件做读写操作时,是否使用缓冲区,建议使用缓冲区
    	# 0(或者 False),则表示在打开指定文件时不使用缓冲区;大于1的整数,该整数用于指定缓冲区的大小(单位是字节);负数,则代表使用默认的缓冲区大小。一般默认即可
    	# 计算机内存的 I/O 速度仍远远高于计算机外设(例如键盘、鼠标、硬盘等)的 I/O 速度,如果不使用缓冲区,则程序在执行 I/O 操作时,内存和外设就必须进行同步读写操作
    	# 也就是说,内存必须等待外设输入(输出)一个字节之后,才能再次输出(输入)一个字节。这意味着,内存中的程序大部分时间都处于等待状态。
    	# 如果使用缓冲区,则程序在执行输出操作时,会先将所有数据都输出到缓冲区中,然后继续执行其它操作,缓冲区中的数据会有外设自行读取处理;
    	# 同样,当程序执行输入操作时,会先等外设将数据读入缓冲区中,无需同外设做同步读写操作。
    # encoding:手动设定打开文件时所使用的编码格式
    
    # 常用属性
    # 输出文件是否已经关闭
    print(file.closed)
    # 输出访问模式
    print(file.mode)
    #输出编码格式
    print(file.encoding)
    # 输出文件名
    print(file.name)
    
    # 文件读取, 二进制和普通模式有啥区别?  对换行符的处理有区别
    # 在 Windows 系统中,文件中用 "\r\n" 作为行末标识符(即换行符),当以文本格式读取文件时,会将 "\r\n" 转换成 "\n";
    # 反之,以文本格式将数据写入文件时,会将 "\n" 转换成 "\r\n"。
    # 这种隐式转换换行符的行为,对用文本格式打开文本文件是没有问题的,但如果用文本格式打开二进制文件,就有可能改变文本中的数据(将 \r\n 隐式转换为 \n)。
    # 在 Unix/Linux 系统中,默认的文件换行符就是 \n,因此在 Unix/Linux 系统中文本格式和二进制格式并无本质的区别
    
    # 所以windows里面,建议统一用二进制读取, linux里面,无区别,都可以
    
    # open() 函数打开文件并读取文件中的内容时,总是会从文件的第一个字符(字节)开始读起。
    # 那么,有没有办法可以自定指定读取的起始位置呢?答案是肯定,这就需要移动文件指针的位置。
    # 指针移动之后,用tell()函数,可以获取到指针当前的位置
    # 用seek()函数,可以自定义读取位置
    f = open("a.txt",'r')
    print(f.tell())  # 0
    print(f.read(3))
    print(f.tell())  # 3
    f.seek(4)   
    print(f.tell())  # 4
    
  2. 对已打开文件做读/写操作:读取文件内容可使用 read()、readline() 以及 readlines() 函数;向文件中写入内容,可以使用 write() 函数。

    # 数据读取
    	# read() 函数:逐个字节或者字符读取文件中的内容;
    	# readline() 函数:逐行读取文件中的内容;
    	# readlines() 函数:一次性读取文件中多行内
    
    # read(): 如果是普通文本模式打开的,会逐字符读取, 如果是二进制,则逐字节读取
    # read时, 有时还会报错UnicodeDecodeError,原因在于,目标文件使用的编码格式和 open() 函数打开该文件时使用的编码格式不匹配。需要在open函数的encoding中,指定正确的编码格式
    # readline(): 按行读取内容,遇到\n结束
    # readlines(): 读取多行内容,放到一个列表里面
    
    f = open("my_file.txt",encoding = "utf-8")
    print(f.read(6))
    
    f = open("my_file.txt")
    byt = f.readline()  # 读取一行, 还可以加最大读取限制f.readline(6)
    print(byt)
    
    f = open("my_file.txt",'rb')
    byt = f.readlines()
    print(byt)  #  [b"xxx", b"xxx"]
    
    # 写入数据
    	# write()函数: 写入内容
    	# writelines(): 逐行写入, 可以实现将字符串列表写入文件中, 需要注意的是,使用 writelines() 函数向文件中写入多行数据时,不会自动给各行添加换行符
    	
    f = open("a.txt", 'w')
    # f = open("a.txt", 'a)   # 追加写入
    f.write("写入一行新数据")
    # 执行操作
    f.flush()  
    # 或者
    f.close()
    
    # 在写入文件完成后,一定要调用 close() 函数将打开的文件关闭,否则写入的内容不会保存到文件中。
    # 这是因为,当我们在写入文件内容时,操作系统不会立刻把数据写入磁盘,而是先缓存起来(放入缓冲区中),只有调用 close() 函数时,操作系统才会保证把没有写入的数据全部写入磁盘文件中。
    # 如果不想立即关闭文件,用f.flush()也行
    
    # 所以,如果写入文本内容时, 缓冲区如果设置成0, 就会报错
    f = open("a.txt", 'w',buffering = 0)  # ValueError: can't have unbuffered text I/O
    
    
  3. 关闭文件:完成对文件的读/写操作之后,最后需要关闭文件,可以使用 close() 函数。

    注意,使用 open() 函数打开的文件对象,必须手动进行关闭,Python 垃圾回收机制无法自动回收打开文件所占用的资源

    # 文件在打开并操作完成之后,就应该及时关闭,否则程序的运行可能出现问题
    file.close()
    
    # 如果不关闭,后面想删除文件,会报错
    # 如果不关闭, 写数据的时候,会写不进去,在向以文本格式(而不是二进制格式)打开的文件中写入数据时,Python 出于效率的考虑,会先将数据临时存储到缓冲区中,
    # 只有使用 close() 函数关闭文件时或者clush()刷新缓冲区时,才会将缓冲区中的数据真正写入文件中
    

3.3 python的with…as机制

任何一门编程语言中,文件的输入输出、数据库的连接断开等,都是很常见的资源管理操作。

但资源都是有限的,完成资源使用后,必须释放出来,不然就容易造成资源泄露,轻者使系统处理缓慢,严重时会使系统崩溃。

但有时候即便使用 close() 做好了关闭文件的操作,如果在打开文件或文件操作过程中抛出了异常,还是无法及时关闭文件。

为了更好地避免此类问题,不同的编程语言都引入了不同的机制。python中对应的解决方式是使用 with…as…. 语句操作上下文管理器(context manager),它能够帮助我们自动分配并且释放资资源。

# 使用 with as 操作已经打开的文件对象(本身就是上下文管理器),无论期间是否抛出异常,都能保证 with as 语句执行完毕后自动关闭已经打开的文件。
with 表达式 [as target]:
    代码块
 
with open('a.txt', 'a') as f:
    f.write("\nxxx")

# 这个不用调用f.close就会自动写入到文件

这个用起来比较简单, 下面看下内部的原理。 首先啥叫上下文管理器呢?

简单的理解,同时包含 __enter__()__exit__() 方法的对象就是上下文管理器。也就是说,上下文管理器必须实现如下两个方法:

  1. __enter__(self):进入上下文管理器自动调用的方法,该方法会在 with...as 代码块执行之前执行。如果 with 语句有 as子句,那么该方法的返回值会被赋值给 as 子句后的变量;该方法可以返回多个值,因此在 as 子句后面也可以指定多个变量(多个变量必须由“()”括起来组成元组)。
  2. __exit__(self, exc_type, exc_value, exc_traceback):退出上下文管理器自动调用的方法。该方法会在 with…as 代码块执行之后执行。如果 with…as 代码块成功执行结束,程序自动调用该方法,调用该方法的三个参数都为 None:如果 with…as 代码块因为异常而中止,程序也自动调用该方法,使用 sys.exc_info 得到的异常信息将作为调用该方法的参数。

当 with as 操作上下文管理器时,就会在执行语句体之前,先执行上下文管理器的 __enter__() 方法,然后再执行语句体,最后执行__exit__()方法

# 构建上下文管理器,有两种方法

# 1. 基于类的上下文管理器
class FkResource:
    def __init__(self, tag):
        self.tag = tag
        print('构造器,初始化资源: %s' % tag)
    # 定义__enter__方法,with体之前的执行的方法
    def __enter__(self):
        print('[__enter__ %s]: ' % self.tag)
        # 该返回值将作为as子句中变量的值
        return 'fkit'  # 可以返回任意类型的值
    # 定义__exit__方法,with体之后的执行的方法
    def __exit__(self, exc_type, exc_value, exc_traceback):
        print('[__exit__ %s]: ' % self.tag)
        # exc_traceback为None,代表没有异常
        if exc_traceback is None:
            print('没有异常时关闭资源')
        else:
            print('遇到异常时关闭资源')
            return False   # 可以省略,默认返回None也被看做是False
            
with FkResource('孙悟空') as dr:
    print(dr)
    print('[with代码块] 没有异常')
print('------------------------------')
with FkResource('白骨精'):
    print('[with代码块] 异常之前的代码')
    raise Exception
    print('[with代码块] ~~~~~~~~异常之后的代码')

构造器,初始化资源: 孙悟空
[__enter__ 孙悟空]: 
fkit
[with代码块] 没有异常
[__exit__ 孙悟空]: 
没有异常时关闭资源
------------------------------
构造器,初始化资源: 白骨精
[__enter__ 白骨精]: 
[with代码块] 异常之前的代码
[__exit__ 白骨精]: 
遇到异常时关闭资源
Traceback (most recent call last):
  File "script.py", line 25, in 
    raise Exception
Exception

# 从上面的输出结果来看,使用 with as 语句管理资源,无论代码块是否有异常,程序总可以自动执行 __exit__() 方法
# 注意,当出现异常时,如果 __exit__ 返回 False(默认不写返回值时,即为 False),则会重新抛出异常,让 with as 之外的语句逻辑来处理异常;
# 反之,如果返回 True,则忽略异常,不再对异常进行处理

# 2. 基于生成器的上下文管理器
from contextlib import contextmanager
@contextmanager
def file_manager(name, mode):
    try:
        f = open(name, mode)
        yield f
    finally:
        f.close()
       
with file_manager('a.txt', 'w') as f:
    f.write('hello world')

# 函数 file_manager() 就是一个生成器,当我们执行 with as 语句时,便会打开文件,并返回文件对象 f;当 with 语句执行完后,finally 中的关闭文件操作便会执行
# 基于类的上下文管理器和基于生成器的上下文管理器,这两者在功能上是一致的。
# 基于类的上下文管理器更加灵活,适用于大型的系统开发,而基于生成器的上下文管理器更加方便、简洁,适用于中小型程序。
# 但是,无论使用哪一种,不用忘记在方法“__exit__()”或者是 finally 块中释放资源,这一点尤其重要。

3.4 文件处理方面的常用模块汇总

3.4.1 pickle模块

pickle模块能够实现任意对象与文本之间的相互转化,也可以实现任意对象与二进制之间的相互转化。也就是说,pickle 可以实现 Python 对象的存储及恢复。

# 1. 基于内存的 Python 对象与二进制互转
	# dumps():将 Python 中的对象序列化成二进制对象,并返回;
	# loads():读取给定的二进制对象数据,并将其转换为 Python 对象;
port pickle
tup1 = ('I love Python', {1,2,3}, None)
#使用 dumps() 函数将 tup1 转成 p1
p1 = pickle.dumps(tup1)
print(p1)
#使用 loads() 函数将 p1 转成 Python 对象
t2 = pickle.loads(p1)
print(t2)

# 2. 基于文件的 Python 对象与二进制互转
	# dump():将 Python 中的对象序列化成二进制对象,并写入文件;
	# load():读取指定的序列化数据文件,并返回对象
import pickle
tup1 = ('I love Python', {1,2,3}, None)
#使用 dumps() 函数将 tup1 转成 p1
with open ("a.txt", 'wb') as f: #打开文件
    pickle.dump(tup1, f) #用 dump 函数将 Python 对象转成二进制对象文件
with open ("a.txt", 'rb') as f: #打开文件
    t3 = pickle.load(f) #将二进制文件对象转换成 Python 对象
    print(t3)

# 这个简单看看, 用的不是很多了, 原因是不能在多语言之间共享,另外 pickle 不支持并发地访问持久性对象,在复杂的系统环境下,尤其是读取海量数据时,使用 pickle 会使整个系统的I/O读取性能成为瓶颈。

3.4.2 fileinput模块

fileinput模块:里面有个input函数能同时打开指定的多个文件,还可以逐个读取这些文件中的内容

import fileinput
#使用for循环遍历 fileinput 对象
for line in fileinput.input(files=('my_file.txt', 'file.txt')):
    # 输出读取到的内容
    print(line)
# 关闭文件流
fileinput.close()

# 这里面一个简单的应用就是先用glob扫到某个目录下面所有txt文件,然后组成一个tuple, 再用这个函数去读取

3.4.3 os.path模块

os.path模块: 这个有一些路径判断好用的函数整理一下

# os.path.abspath(path)	返回 path 的绝对路径。
# os.path.basename(path)	获取 path 路径的基本名称,即 path 末尾到最后一个斜杠的位置之间的字符串。
# os.path.commonprefix(list)	返回 list(多个路径)中,所有 path 共有的最长的路径。
# os.path.dirname(path)	返回 path 路径中的目录部分。
# os.path.exists(path)	判断 path 对应的文件是否存在,如果存在,返回 True;反之,返回 False。和 lexists() 的区别在于,exists()会自动判断失效的文件链接(类似 Windows 系统中文件的快捷方式),而 lexists() 却不会。
# os.path.getatime(path)	返回 path 所指文件的最近访问时间(浮点型秒数)。
# os.path.getmtime(path)	返回文件的最近修改时间(单位为秒)。
# os.path.getctime(path)	返回文件的创建时间(单位为秒,自 1970 年 1 月 1 日起(又称 Unix 时间))。
# os.path.getsize(path)	返回文件大小,如果文件不存在就返回错误。

# os.path.isabs(path)	判断是否为绝对路径。
# os.path.isfile(path)	判断路径是否为文件。
# os.path.isdir(path)	判断路径是否为目录。

# os.path.join(path1[, path2[, ...]])	把目录和文件名合成一个路径。
# os.path.realpath(path)	返回 path 的真实路径。
# os.path.relpath(path[, start])	从 start 开始计算相对路径。
# os.path.samefile(path1, path2)	判断目录或文件是否相同。
# os.path.split(path)	把路径分割成 dirname 和 basename,返回一个元组。

# os.path.walk(path, visit, arg)	遍历path,进入每个目录都调用 visit 函数,visit 函数必须有 3 个参数(arg, dirname, names),dirname 表示当前目录的目录名,names 代表当前目录下的所有文件名,args 则为 walk 的第三个参数。

3.4.4 tempfile模块

tempfile模块: 专门用于创建临时文件和临时目录, 这个实际中还是比较常用的,临时存储一下很方便

# 常用方法
# tempfile.TemporaryFile(mode='w+b', buffering=None, encoding=None, newline=None, suffix=None, prefix=None, dir=None)	创建临时文件。该函数返回一个类文件对象,也就是支持文件 I/O。
# tempfile.NamedTemporaryFile(mode='w+b', buffering=None, encoding=None, newline=None, suffix=None, prefix=None, dir=None, delete=True)	创建临时文件。该函数的功能与上一个函数的功能大致相同,只是它生成的临时文件在文件系统中有文件名。
# tempfile.TemporaryDirectory(suffix=None, prefix=None, dir=None)	生成临时目录。
# tempfile.gettempdir()	获取系统的临时目录。

# temporarydictory的使用: 
# 我一般喜欢存储文件到云的时候,用这个在本地做一层缓存
def _save_csv(self, df):
    with tempfile.TemporaryDirectory() as tmpdirname:
        temp_file = os.path.join(tmpdirname, 'tag_statistic.csv')
        df.to_csv(temp_file, encoding='utf-8-sig')
        tag_file = os.path.join(self._mount_path, self._workdir, self._input_path, "tag_statistic.csv")
        with open(temp_file, 'rb') as f:
            data_bytes = f.read()
        with open(tag_file, "wb") as f:
            f.write(data_bytes)
        # 上传到云
        upload_ks3(temp_file, remote_file)

# 从远程下载压缩包解压,我喜欢用临时目录存储,处理
with TemporaryDirectory() as temp_dir:
    urllib.request.urlretrieve(artifact_url, f"{temp_dir}/artifact_tar.tar.gz")
    with tarfile.open(f"{temp_dir}/artifact_tar.tar.gz") as f:
        base_dir = f"{temp_dir}/artifact"
        os.mkdir(base_dir)
        f.extractall(base_dir)
        
        # 处理里面的文件

# 临时文件 上传到云用
with tempfile.NamedTemporaryFile(mode='w') as temp_file:
    json.dump(json_list, temp_file)
    temp_file.seek(0)
    ks3_util = KS3Util(env=KS3EnvEnum.online.value, bucket=KS3BucketEnum.ad_warehouse_online.value)
    ks3_util.upload_file(temp_file.name, ks3_file)

3.5 常见文件格式的写入和读取

这里整理python读写不同格式常用的代码模板。

# json文件:这个非常好用, 也是我现在最常用的方式
# 原因是json文件内容灵活,什么字典,列表等都可以存进去,读起来也方便,另外跨语言也没问题, 传输也方便
**#** 读取JSON文件
****with open('example.json', 'r') as file:
    data = json.load(file)  # load方法用于从一个文件句柄中读取JSON数据。
    # 如果这里用json.loads,要这样写
    # data = json.loads(file.read())  # 用于将一个JSON格式的字符串转换为Python的数据结构, loads loading string的缩写
    print(data)

with open('xxx.json', 'w') as f:
	f.write(json.dumps(data))
	# or json.dump(data, f)

# txt文件
with open('example.txt', 'r') as file:
    content = file.read()
    print(content)
# 写入文本文件
with open('example.txt', 'w') as file:
    file.write("Hello, World!")

4. 小总

这篇文章算是作为python过程中的一些辅助了,在实际的项目中, 这两块内容还是很重要的,异常处理可以帮助我们更好的掌控代码, 写出鲁棒性的代码, 而文件操作可以帮助我们对数据更好的读取处理, 都是一些非常实用的内容。

参考

  • C语言中文网教程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1832235.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

做恒指交易一定要有耐心

1、记住成为赢利的交易者是一个旅程&#xff0c;而非目的地。世界上并不存在只赢不输的交易者。试着每天交易的更好一些&#xff0c;从自己的进步中得到乐趣。聚精会神学习技术分析的技艺&#xff0c;提高自己的交易技巧&#xff0c;而不是仅仅把注意力放在自己交易输赢多少上。…

轮式机器人Swiss-Mile城市机动性大提升:强化学习引领未来城市物流

喜好儿小斥候消息&#xff0c;苏黎世联邦理工学院的研究团队成功开发了一款革命性的机器人控制系统&#xff0c;该系统采用强化学习技术&#xff0c;使轮式四足机器人在城市环境中的机动性和速度得到了显著提升。 喜好儿网 这款专为轮腿四足动物设计的控制系统&#xff0c;能…

eNSP学习——配置基于接口地址池的DHCP

目录 主要命令 原理概述 实验目的 实验内容 实验拓扑 实验编址 实验步骤 1、基本配置 2、基于接口配置 DHCP Server 功能 3、配置基于接口的DHCP Server租期/DNS服务器地址 4、配置 DHCP Client 主要命令 //查看DHCP地址池中的地址分配情况 display ip pool//开启D…

【源码】2024运营版多商户客服系统/在线客服系统/手机客服/PC软件客服端

带客服工作台pc软件源代码&#xff0c;系统支持第三方系统携带参数打开客服链接&#xff0c;例如用户名、uid、头像等 支持多商家&#xff08;多站点&#xff09;支持多商家&#xff08;多站点&#xff09;&#xff0c;每个注册用户为一个商家&#xff0c;每个商家可以添加多个…

30.保存游戏配置到文件

上一个内容&#xff1a;29.添加录入注入信息界面 以 29.添加录入注入信息界面 它的代码为基础进行修改 效果图&#xff1a; 首先在我们辅助程序所在目录下创建一个ini文件 文件内容 然后首先编写一个获取辅助程序路径的代码 TCHAR FileModule[0x100]{};GetModuleFileName(NUL…

嵌入式学习记录6.17(qss练习)

一思维导图 二.练习 widget.h #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);this->setWindowFlag(Qt::FramelessWindowHint);this->setAttribute(Qt:…

Java多线程设计模式之保护性暂挂模式

模式简介 多线程编程中&#xff0c;为了提高并发性&#xff0c;往往将一个任务分解为不同的部分。将其交由不同的线程来执行。这些线程间相互协作时&#xff0c;仍然可能会出现一个线程等待另一个线程完成一定的操作&#xff0c;其自身才能继续运行的情形。 保护性暂挂模式&a…

数据治理服务解决方案(35页WORD)

方案介绍&#xff1a; 本数据治理服务解决方案旨在为企业提供一站式的数据治理服务&#xff0c;包括数据规划、数据采集、数据存储、数据处理、数据质量保障、数据安全及合规等方面。通过构建完善的数据治理体系&#xff0c;确保企业数据的准确性、完整性和一致性&#xff0c;…

Excel 识别数据层次后转换成表格

某列数据可分为 3 层&#xff0c;第 1 层是字符串&#xff0c;第 2 层是日期&#xff0c;第 3 层是时间&#xff1a; A1NAME122024-06-03304:06:12404:09:23508:09:23612:09:23717:02:2382024-06-02904:06:121004:09:231108:09:2312NAME2132024-06-031404:06:121504:09:231620…

JPS(Jump Point Search)跳点搜索路径规划算法回顾

本篇文章主要回顾一下几年前学的JPS跳点搜索规划算法的相关内容&#xff0c;之前学的时候没有进行概括总结&#xff0c;现在补上 一、A*算法简单回顾 – 1、基本介绍和原理 A*&#xff08;A-Star)算法是一种静态路网中求解最短路径最有效的直接搜索方法&#xff0c;也是解决许多…

RERCS系统开发实战案例-Part06 FPM Application添加列表组件(List UIBB)

在FPM Application中添加搜索结果的List UIBB 1&#xff09;添加List UIBB 2&#xff09;提示配置标识不存在&#xff0c;则需要新建配置标识&#xff08;* 每个组件都必须有对应的配置标识&#xff09;&#xff1b; 3&#xff09;选择对应的包和请求 4&#xff09;为List UIB…

简述spock以及使用

1. 介绍 1.1 Spock是什么&#xff1f; Spock是一款国外优秀的测试框架&#xff0c;基于BDD&#xff08;行为驱动开发&#xff09;思想实现&#xff0c;功能非常强大。Spock结合Groovy动态语言的特点&#xff0c;提供了各种标签&#xff0c;并采用简单、通用、结构化的描述语言…

【软件测试】软件测试入门

软件测试入门 一、什么是软件测试二、软件测试和软件开发的区别三、软件测试在不同类型公司的定位1. 无组织性2. 专职 OR 兼职3. 项目性VS.职能性4.综合型 四、一个优秀的软件测试人员具备的素质1. 技能相关2. 非技能相关 一、什么是软件测试 最常见的理解是&#xff1a;软件测…

设备保养计划不再是纸上谈兵,智能系统让执行更到位!

在物业管理的日常工作中&#xff0c;我们常常听到“设备保养台账”“设备保养计划”“设备保养记录”等等这些词&#xff0c;但你是否真正了解它们的含义&#xff1f;是否知道一个完善的设备保养计划、记录、台账对于物业运营的重要性&#xff1f;今天&#xff0c;我们就来深入…

AI产品经理,应掌握哪些技术?

美国的麻省理工学院&#xff08;Massachusetts Institute of Technology&#xff09;专门负责科技成果转化商用的部门研究表明&#xff1a; 每一块钱的科研投入&#xff0c;需要100块钱与之配套的投资&#xff08;人、财、物&#xff09;&#xff0c;才能把思想转化为产品&…

《纪元 1800》好玩吗? 苹果电脑能玩《纪元 1800》吗?

《纪元1800》是一款不错的策略游戏&#xff0c;这款游戏因为画面和玩法独特深受玩家们的喜爱。下面我们来看看《纪元 1800》好玩吗&#xff0c;苹果电脑能玩《纪元 1800》吗的相关内容。 一、《纪元1800》好玩吗 《纪元1800》是一款备受瞩目的策略游戏。下面让我们来看看这款…

mysql [Err] 1118 - Row size too large (> 8126).

1.找到my.ini文件 1.1 控制台输入以下指令&#xff0c;打开服务 services.msc1.2 查看mysql服务的属性 2.停止mysql服务&#xff0c;修改my.ini文件并且保存 innodb_strict_mode03.重启mysql服务 4.验证是否关闭成功 show variables like %innodb_strict_mode%; show vari…

SaaS产品运营|一文讲清楚为什么ToB产品更适合采用PLG模式?

在数字化时代&#xff0c;ToB&#xff08;面向企业&#xff09;产品市场的竞争愈发激烈。为了在市场中脱颖而出&#xff0c;许多企业开始转向PLG&#xff08;产品驱动增长&#xff09;模式。这种模式以产品为核心&#xff0c;通过不断优化产品体验来驱动用户增长和业务发展。本…

学本领、争奖金! 由和鲸支持的“数据蜂杯”全国大学生暑期面访调查大赛火热报名中

随着数字时代的到来&#xff0c;社会调查能力、数据分析能力成为当代大学生不可或缺的核心素养。为了进一步提升当代大学生深入田野、以团队的方式采集高质量数据的能力&#xff0c;中国人民大学中国调查与数据中心&#xff08;NSRC&#xff09;举办“数据蜂杯”全国大学生暑期…

头歌资源库(9)丢失的数字

一、 问题描述 二、算法思想 输入n和nums数组。初始化一个大小为n1的数组counts&#xff0c;初始值都为0。遍历nums数组&#xff0c;将counts[nums[i]]的值加1。遍历counts数组&#xff0c;找到第一个值为0的索引&#xff0c;即为没有出现在数组中的那个数。输出结果。 三、…