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

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语言中文网教程

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

小区噪音监测管理系统设计

一、引言 随着城市化进程的加快&#xff0c;小区居民对于居住环境的要求日益提高。其中&#xff0c;噪音污染已成为影响居民生活质量的重要因素。因此&#xff0c;设计一套小区噪音监测管理系统&#xff0c;对于提升居民的生活品质和小区管理效率具有重要意义。本文将详细阐述…

如何拥有自己的微信小程序

如何拥有自己的微信小程序 ~~话先放在这里~~ 写在前面申请一个属于自己的小程序先去[微信开放平台](https://open.weixin.qq.com/home)申请一个你的小程序扫码申请新小程序小程序该记好的个人信息 安装微信开发者工具下载工具关联你的小程序请求域名配置发布小程序 BUY一个自己…

SQL:按用户名复制权限

生产系统中有一个模块是管理用户及菜单权限&#xff0c;它们是由3个数据表组成&#xff0c;关系及字段如下&#xff1a; 原来为每个用户添加菜单的访问权限时都是一个一个添加&#xff0c;但今天遇到有个新来的员工&#xff0c;需要具有与另一个员工相同的权限。新建一个用户后…

PS插件创成式填充功能全面测评:轻松实现AI修图新高度

大家好&#xff0c;我是你们的AIGC测评博主。今天&#xff0c;我将为大家带来一款ps插件创成式填充功能——深度体验 在图像处理领域&#xff0c;AI技术的应用已经越来越广泛。而创成式填充功能&#xff0c;无疑是其中的佼佼者。它利用AI技术&#xff0c;能够根据用户输入的关…

c语言——c51单片机——数码管

数码管&#xff1a; #include "reg51.h"void delay(unsigned int n) {while (n)--n; }void main(void) { //unsigned char num[] {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d,0x7d, 0x07, 0x7f, 0x6f, 0x77, 0x7c,0x39, 0x5e, 0x79, 0x71, 0x00};unsigned int i 0…

坚持刷题|合并有序链表

文章目录 题目思考代码实现迭代递归 扩展实现k个有序链表合并方法一方法二 PriorityQueue基本操作Java示例注意事项 Hello&#xff0c;大家好&#xff0c;我是阿月。坚持刷题&#xff0c;老年痴呆追不上我&#xff0c;消失了一段时间&#xff0c;我又回来刷题啦&#xff0c;今天…

雪花算法和UUID

目录 雪花算法概念优点和不足优点:缺点:解决方案代码示例 UUID优点与不足优点不足 两种算法的比较应用场景区别 雪花算法 概念 雪花算法是一个分布式id生成算法&#xff0c;它生成的id一般情况下具有唯一性。由64位01数字组成&#xff0c;第一位是符号位&#xff0c;始终为0。…

【leetcode刷题】面试经典150题 , 27. 移除元素

leetcode刷题 面试经典150 27. 移除元素 难度&#xff1a;简单 文章目录 一、题目内容二、自己实现代码2.1 方法一&#xff1a;直接硬找2.1.1 实现思路2.1.2 实现代码2.1.3 结果分析 2.2 方法二&#xff1a;排序整体删除再补充2.1.1 实现思路2.1.2 实现代码2.1.3 结果分析 三、…

大模型泡沫退去,谁能活到下半场?

前言 从今年3月开始&#xff0c;国内企业纷纷下场大模型&#xff0c;铆足劲秀肌肉&#xff0c;如今转向垂直行业淘金&#xff0c;试图争霸行业大模型。我们的心态也逐渐从看乐子&#xff0c;到严肃讨论。 在人工智能的世界&#xff0c;我们经历了众多的概念游戏&#xff0c;在…

shell编程——脚本入门

在编写脚本的时候指定解析器 在编写shell脚本时第一行以#&#xff01;/bin/bash开头指定解析器。 在shell脚本中使用echo语句来在屏幕中打印内容。 调用shell脚本的第一种方式 在shell脚本中以bash或者是sh脚本路径的方式来启动脚本。这种执行脚本的方式是在Linux操作系统的b…

【C++高阶】掌握C++多态:探索代码的动态之美

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ ⏩收录专栏⏪&#xff1a;C “ 登神长阶 ” &#x1f921;往期回顾&#x1f921;&#xff1a;C继承 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀继承 &#x1f4d2;1. 多态的定义及实现&…

【总线】AXI总线:FPGA设计中的通信骨干

目录 AXI4&#xff1a;高性能地址映射通信的基石 AXI4-Lite&#xff1a;轻量级但功能强大的通信接口 AXI4-Stream&#xff1a;高速流数据传输的利器 结语&#xff1a;AXI总线在FPGA设计中的重要性 大家好,欢迎来到今天的总线学习时间!如果你对电子设计、特别是FPGA和SoC设计…

Go 并发控制:RWMutex 实战指南

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

怎么管理网站的数据

每一个网站都会有很多的数据&#xff0c;这些数据的来源&#xff0c;有一些是直接把数据存放在运行文件里面&#xff0c;有一些则是存放在数据库里面&#xff0c;如MySQL、SQL Server等等&#xff0c;这些数据库都是需要安装指定的数据库环境才能运行起来&#xff0c;数据库的存…

减肥药实质利好服装业:身材好了,更时尚了 1-5月份,新建商品房销售面积同比下降20.3%

减肥药实质利好服装业&#xff1a;身材好了&#xff0c;更时尚了 减肥成功的顾客纷纷瞄准性感look&#xff0c;不但促进了销售&#xff0c;还给服装品牌节省了成本&#xff0c;因为小尺寸的衣服使用的面料更少。大码女装&#xff0c;可能是下一个被 GLP-1减肥神药杀死的行业。…

【计算机毕业设计】234基于微信小程序的中国各地美食推荐平台

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

小知识点快速总结:梯度爆炸和梯度消失的原理和解决方法

本系列文章只做简要总结&#xff0c;不详细说明原理和公式。 目录 1. 参考文章2. 反向梯度求导推导3. 具体分析3.1 梯度消失的原理3.2 梯度爆炸的原理 4. 解决方法 1. 参考文章 [1] shine-lee, "网络权重初始化方法总结&#xff08;上&#xff09;&#xff1a;梯度消失、…

Elixir学习笔记——速构(函数式编程基础)

在 Elixir 中&#xff0c;循环遍历 Enumerable 是很常见的&#xff0c;通常会过滤掉一些结果并将值映射到另一个列表中。 速构是此类构造的语法糖&#xff1a;它们将这些常见任务分组为 for 特殊形式。 例如&#xff0c;我们可以将一串整数映射到它们的平方值&#xff1a; 速构…

VSCode的maven插件配置问题

最近尝试使用VSCode开发java后台项目&#xff0c;发现安装了java开发套件的插件 配置了开发环境之后&#xff0c;maven下载的依赖包始终位于~/.m2/repository目录之后&#xff0c;放在了默认的C盘&#xff0c;这就是我最不喜欢的位置。 为了保证C的小&#xff0c;所以需要修改…

四川赤橙宏海商务信息咨询有限公司抖音电商服务领军企业

在当今数字化浪潮中&#xff0c;电商行业正以前所未有的速度蓬勃发展&#xff0c;而抖音电商作为其中的佼佼者&#xff0c;更是吸引了无数商家的目光。在这个充满机遇与挑战的市场中&#xff0c;四川赤橙宏海商务信息咨询有限公司凭借其专业的服务和深厚的行业底蕴&#xff0c;…