4、异常、日志

try-except-finally

可以通过try-except-finally对错误进行捕获和处理

当认为某些代码可能会出错时,就可以用try来运行这段代码,如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即except语句块,执行完except后,如果有finally语句块,则执行finally语句块,至此,执行完毕。

try:
    a = 1 / 0
# 处理除零错误
except ZeroDivisionError as e:
    print(e)
finally:
    print('is finally')

'''
division by zero
is finally
'''

处理多个错误

try:
    a = 1 / int('a')
# 处理除零错误
except ZeroDivisionError as e:
    print(e)
# 处理参数错误
except ValueError as e:
    print(e)
finally:
    print('is finally')

'''
invalid literal for int() with base 10: 'a'
is finally
'''

可以在except语句块后面添加else语句块,如果没有错误发生,将执行else语句块

try:
    a = 1 / int('1')
# 处理除零错误
except ZeroDivisionError as e:
    print(e)
# 处理参数错误
except ValueError as e:
    print(e)
else:
    print('is else')
finally:
    print('is finally')

'''
is else
is finally
'''

with

with 语句是 Python 中用于简化资源管理的语法糖。它确保在进入代码块时自动获取资源,并在退出代码块时自动释放资源。常见的资源包括文件、网络连接、数据库连接等。with 语句的核心思想是“上下文管理”,即在一定范围内自动处理资源的获取和释放,避免了手动管理资源带来的复杂性和潜在错误。

总结起来使用python 提供的with主要的作用是:

常见的几个应用场景有:

1、文件操作。

2、进程线程之间互斥对象。

3、支持上下文其他对象

4、需要进行资源链接和结束的时候进行释放操作

5、释放锁

6、创建代码补丁

with 语句依赖于 上下文管理器(Context Manager),这是一个实现了 __enter____exit__ 方法的对象。__enter__ 方法在进入 with 代码块时调用,通常用于获取资源;__exit__ 方法在退出 with 代码块时调用,通常用于释放资源。

with 语句的基本语法如下:

with context_manager as variable:
    # 执行代码块

其中,context_manager 是一个实现了上下文管理协议的对象,variable 是可选的,用于接收 __enter__ 方法返回的值。

with 语句的常见用法

文件操作

with open('example.txt', 'r') as file:
    content = file.read()
    print(content)

网络连接

import requests

with requests.Session() as session:
    response = session.get('https://api.example.com/data')
    print(response.json())

数据库连接

import sqlite3

with sqlite3.connect('example.db') as conn:
    cursor = conn.cursor()
    cursor.execute('SELECT * FROM users')
    rows = cursor.fetchall()
    for row in rows:
        print(row)

自定义上下文管理器

import time

class Timer:
    def __enter__(self):
        self.start_time = time.time()
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        end_time = time.time()
        elapsed_time = end_time - self.start_time
        print(f"Elapsed time: {elapsed_time:.2f} seconds")

# 使用自定义上下文管理器
with Timer():
    time.sleep(2)

with语句的高级用法

多个上下文管理器

with 语句支持同时管理多个上下文管理器,只需将它们用逗号分隔即可。这对于需要同时管理多个资源的场景非常有用。

with open('file1.txt', 'r') as f1, open('file2.txt', 'r') as f2:
    content1 = f1.read()
    content2 = f2.read()
    if content1 == content2:
        print("Files are identical")
    else:
        print("Files are different")

异常处理

with 语句不仅可以管理资源,还可以捕获和处理异常。__exit__ 方法可以接受三个参数:exc_typeexc_valuetraceback,分别表示异常类型、异常值和堆栈跟踪。如果 __exit__ 方法返回 True,则表示异常已被处理,不会传播到外部;如果返回 False 或不返回任何值,则异常会继续传播。

假设我们想在文件读取过程中捕获并处理 FileNotFoundError 异常。我们可以在自定义上下文管理器中实现这一功能。

class FileOpener:
    def __init__(self, filename):
        self.filename = filename
        self.file = None

    def __enter__(self):
        try:
            self.file = open(self.filename, 'r')
            return self.file
        except FileNotFoundError:
            print(f"File {self.filename} not found")
            return None

    def __exit__(self, exc_type, exc_value, traceback):
        if self.file:
            self.file.close()

# 使用自定义上下文管理器
with FileOpener('nonexistent.txt') as file:
    if file:
        content = file.read()
        print(content)
    else:
        print("File not found, skipping...")

python错误继承关系

BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StopAsyncIteration
      +-- ArithmeticError
      |    +-- FloatingPointError
      |    +-- OverflowError
      |    +-- ZeroDivisionError
      +-- AssertionError
      +-- AttributeError
      +-- BufferError
      +-- EOFError
      +-- ImportError
      |    +-- ModuleNotFoundError
      +-- LookupError
      |    +-- IndexError
      |    +-- KeyError
      +-- MemoryError
      +-- NameError
      |    +-- UnboundLocalError
      +-- OSError
      |    +-- BlockingIOError
      |    +-- ChildProcessError
      |    +-- ConnectionError
      |    |    +-- BrokenPipeError
      |    |    +-- ConnectionAbortedError
      |    |    +-- ConnectionRefusedError
      |    |    +-- ConnectionResetError
      |    +-- FileExistsError
      |    +-- FileNotFoundError
      |    +-- InterruptedError
      |    +-- IsADirectoryError
      |    +-- NotADirectoryError
      |    +-- PermissionError
      |    +-- ProcessLookupError
      |    +-- TimeoutError
      +-- ReferenceError
      +-- RuntimeError
      |    +-- NotImplementedError
      |    +-- RecursionError
      +-- SyntaxError
      |    +-- IndentationError
      |         +-- TabError
      +-- SystemError
      +-- TypeError
      +-- ValueError
      |    +-- UnicodeError
      |         +-- UnicodeDecodeError
      |         +-- UnicodeEncodeError
      |         +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
           +-- ImportWarning
           +-- UnicodeWarning
           +-- BytesWarning
           +-- EncodingWarning
           +-- ResourceWarning

日志 logging

如果不捕获错误,自然可以让Python解释器来打印出错误堆栈,但程序也被结束了。既然我们能捕获错误,就可以把错误堆栈打印出来,然后分析错误原因,同时,让程序继续执行下去。

Python内置的logging模块可以非常容易地记录错误信息

import logging

try:
    a = 1 / 0
except BaseException as e:
    print(e)
    logging.exception(e)
finally:
    print('is finally')

'''
division by zero
ERROR:root:division by zero
Traceback (most recent call last):
  File "d:\python_project\demo\main.py", line 4, in <module>
    a = 1 / 0
ZeroDivisionError: division by zero
is finally
'''

logging错误级别

CRITICAL > ERROR > WARNING > INFO > DEBUG

debug : 打印全部的日志,详细的信息,通常只出现在诊断问题上

info : 打印info,warning,error,critical级别的日志,确认一切按预期运行

warning : 打印warning,error,critical级别的日志,一个迹象表明,一些意想不到的事情发生了,或表明一些问题在不久的将来(例如。磁盘空间低”),这个软件还能按预期工作

error : 打印error,critical级别的日志,更严重的问题,软件没能执行一些功能,exception属于error,但是会显示堆栈

critical : 打印critical级别,一个严重的错误,这表明程序本身可能无法继续运行

常用api

Logging.Formatter # 这个类配置了日志的格式,在里面自定义设置日期和时间,输出日志的时候将会按照设置的格式显示内容。


Logging.Logger # Logger是Logging模块的主体,进行以下三项工作:
# 1. 为程序提供记录日志的接口
# 2. 判断日志所处级别,并判断是否要过滤
# 3. 根据其日志级别将该条日志分发给不同handler
# Logger常用函数有:
Logger.setLevel() # 设置日志级别
Logger.addHandler() # 和 Logger.removeHandler() 添加和删除一个Handler
Logger.addFilter() # 添加一个Filter,过滤作用


Logging.Handler # Handler基于日志级别对日志进行分发,如设置为WARNING级别的Handler只会处理WARNING及以上级别的日志。
# Handler常用函数有:
setLevel() 设置级别
setFormatter() 设置Formatter

输出日志到文件

import logging

#创建一个logger
logger = logging.getLogger()
#Log等级总开关
logger.setLevel(logging.INFO)
# 创建一个handler,用于写入日志文件,mode='w'表示程序重启重写日志
fh = logging.FileHandler('./logging.log',mode='w')
# 定义handler的输出格式
formatter = logging.Formatter("%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s")
fh.setFormatter(formatter)
# 输出到file的log等级的开关
fh.setLevel(logging.ERROR)
logger.addHandler(fh)

try:
    a = 1 / 0
except BaseException as e:
    print(e)
    logging.exception(e)
finally:
    print('is finally')

'''
logging.py文件:
2022-02-14 21:11:28,643 - main.py[line:20] - ERROR: division by zero
Traceback (most recent call last):
  File "d:\python_project\demo\main.py", line 17, in <module>
    a = 1 / 0
ZeroDivisionError: division by zero
'''

format常用格式

%(levelno)s # 打印日志级别的数值
%(levelname)s # 打印日志级别名称
%(pathname)s # 打印当前执行程序的路径,其实就是sys.argv[0]
%(filename)s # 打印当前执行程序名
%(funcName)s # 打印日志的当前函数
%(lineno)d # 打印日志的当前行号
%(asctime)s # 打印日志的时间
%(thread)d # 打印线程ID
%(threadName)s # 打印线程名称
%(process)d # 打印进程ID
%(message)s # 打印日志信息

自定义错误、抛出错误

错误也是一个class,所以需要继承一个合适的错误类

使用raise可以抛出一个错误

class NameError(ValueError):
    pass

def setName(name):
    if(len(name) > 4):
        raise NameError('名称不合法:%s' % name)

setName('一二三四五')

'''
Traceback (most recent call last):
  File "d:\python_project\demo\main.py", line 11, in <module>
    setName('一二三四五')
  File "d:\python_project\demo\main.py", line 9, in setName
    raise NameError('名称不合法:%s' % name)
__main__.NameError: 名称不合法:一二三四五
'''