python入门(13)异常与文件

1. 异常

1.1 异常处理机制

在 Python 中,异常是指程序在运行过程中出现的错误或异常情况。当程序执行到出现错误的代码时,会引发异常,并导致程序中断执行。异常的出现可能是由于程序的错误、不合理的输入、外部环境的变化等引起的。

异常处理机制是一种在程序中处理异常情况的机制。它可以捕获和处理异常,使程序能够在出现异常时进行适当的处理,而不会导致程序崩溃。通过使用异常处理机制,可以增强程序的稳定性和容错性。

1.2 常见的异常类型

Python 提供了多种内置的异常类型,用于表示不同类型的错误和异常情况。一些常见的异常类型包括:

  • TypeError:类型错误,表示操作或函数应用于不适当的数据类型。
  • ValueError:值错误,表示传递给函数的参数具有无效的值。
  • FileNotFoundError:文件未找到错误,表示尝试访问不存在的文件。
  • IndexError:索引错误,表示使用无效的索引访问序列或列表。
  • KeyError:键错误,表示使用字典中不存在的键访问字典元素。

1.3 异常处理语句

异常处理语句主要包括以下几种:

(1)try-except语句:用于捕获并处理异常。try 代码块中包含可能引发异常的代码,而 except 代码块中包含处理异常的代码。语法如下:

try:
    # 可能引发异常的代码
except ExceptionType:
    # 处理异常的代码

当发生异常时,程序会跳转到 except 代码块,并执行其中的代码。

(2)else子句:在 try-except 语句中可以使用 else 子句,用于指定在没有发生异常时要执行的代码。如果 try 代码块中没有发生异常,那么 else 子句中的代码会被执行。语法如下:

try:
    # 可能引发异常的代码
except ExceptionType:
    # 处理异常的代码
else:
    # 没有异常时要执行的代码

(3)finally子句finally 子句用于指定无论是否发生异常,都会执行的代码块。无论异常是否被捕获和处理,finally 子句中的代码都会被执行。语法如下:

try:
    # 可能引发异常的代码
except ExceptionType:
    # 处理异常的代码
finally:
    # 无论是否发生异常都要执行的代码

(4)抛出异常:除了捕获异常外,我们还可以使用 raise 语句来手动抛出异常。通过抛出异常,我们可以在特定条件下中断程序的执行,并将异常信息传递给上层调用者或处理其他逻辑。使用 raise 语句可以抛出特定的异常类型,或者自定义异常类型。

抛出异常的语法如下:

raise ExceptionType("Exception message")

其中,ExceptionType 是要抛出的异常类型,可以是内置的异常类型,也可以是自定义的异常类型。"Exception message" 是异常的错误消息,用于提供有关异常的额外信息。

1.4 访问异常的相关信息

Python中提供了获取异常信息的机制。当捕获到异常时,可以使用except语句将异常对象赋值给一个变量,然后通过该变量访问异常的相关信息。

示例如下:

try:
    # 代码块
except Exception as e:
    # 处理异常
    error_message = str(e)  # 获取异常信息
    print("Exception message:", error_message)

在上述示例中,except语句捕获异常并将其赋值给变量e。通过调用str()函数,可以将异常对象转换为字符串类型,从而获取异常信息。然后可以根据需要进行处理或输出异常信息。

需要注意的是,异常信息的具体内容取决于异常的类型和抛出时提供的错误消息。一般情况下,异常对象的字符串表示会包含有关异常的信息,如错误消息、堆栈跟踪等。

1.5 异常链

异常链(Exception Chaining)是指在异常处理过程中,当一个异常引发另一个异常时,保留原始异常信息的机制。Python提供了异常链的支持,允许开发者在捕获和处理异常时保留原始异常的上下文信息。

通过异常链,我们可以追溯异常发生的原因,从而更好地理解程序的错误行为。异常链的特点是每个异常都会有一个原因(cause),这个原因可以是另一个异常对象。

下面是一个示例代码,演示了异常链的使用:

def process_data(data):
    try:
        # 处理数据的代码
        result = data / 0  # 这里故意引发一个除零异常
        return result
    except Exception as e:
        # 捕获异常并抛出新的异常,保留原始异常信息
        raise ValueError("Data processing failed") from e

def main():
    try:
        data = 10
        process_data(data)
    except ValueError as e:
        print("Error:", str(e))
        # 访问原始异常信息
        original_exception = e.__cause__
        if original_exception:
            print("Original exception:", str(original_exception))

main()

在上述示例中,process_data()函数用于处理数据,其中故意引发了一个除零异常。在捕获异常时,使用raise语句抛出一个新的ValueError异常,并通过from关键字将原始异常对象传递给新的异常对象,形成了异常链。

main()函数中,捕获到ValueError异常,并输出异常信息。通过访问__cause__属性,可以获取原始异常对象,并打印原始异常信息。

异常链的使用可以帮助我们追踪和调试程序中的异常情况,更好地定位问题的根本原因。

1.6 自定义异常

在Python中,我们可以自定义异常类来满足特定的异常情况。自定义异常可以继承自内置的异常类或其他已定义的异常类,以便更好地区分和处理特定类型的错误。

下面是一个示例代码,演示了如何定义和使用自定义异常:

class CustomError(Exception):
    def __init__(self, message):
        self.message = message

    def __str__(self):
        return self.message

def divide_numbers(a, b):
    if b == 0:
        raise CustomError("Cannot divide by zero")
    return a / b

def main():
    try:
        result = divide_numbers(10, 0)
        print("Result:", result)
    except CustomError as e:
        print("Error:", str(e))

main()

在上述示例中,我们定义了一个名为CustomError的自定义异常类。它继承自内置的Exception类,并添加了__init____str__方法用于初始化异常对象和返回异常信息。

divide_numbers()函数中,我们进行了除法运算,如果除数为零,则抛出CustomError异常,并传入错误信息。在main()函数中,我们捕获并处理CustomError异常,并打印异常信息。

通过自定义异常,我们可以将错误分类,提供更具体和有意义的错误信息,使得程序的异常处理更加灵活和可读。

需要注意的是,在自定义异常类中,通常会继承自Exception类或其子类,以便与Python标准异常保持一致,并获得异常处理的一些基本功能。同时,自定义异常类可以根据需要添加其他属性和方法,以满足特定的需求。

1.7 简单的用户注册场景示例

当涉及到与外部服务进行通信时,异常处理机制尤为重要。下面是一个示例代码,演示了一个简单的用户注册场景,包括与外部数据库的交互,以及相应的异常处理:

class DatabaseError(Exception):
    def __init__(self, message):
        self.message = message

    def __str__(self):
        return self.message

class UserRegistration:
    def __init__(self, username, password):
        self.username = username
        self.password = password

    def register(self):
        try:
            # 模拟与数据库的交互
            self._save_to_database()
            print("User registration successful!")
        except DatabaseError as e:
            print("User registration failed:", str(e))

    def _save_to_database(self):
        try:
            # 模拟与数据库建立连接
            self._connect_to_database()

            # 模拟将用户信息保存到数据库
            self._insert_user_to_database()

            # 模拟关闭数据库连接
            self._close_database_connection()

        except ConnectionError:
            raise DatabaseError("Failed to connect to the database")
        except ValueError:
            raise DatabaseError("Invalid user data")
        except Exception as e:
            raise DatabaseError("An error occurred while saving user data")

    def _connect_to_database(self):
        # 模拟与数据库建立连接的逻辑
        print("Connecting to the database...")

    def _insert_user_to_database(self):
        # 模拟将用户信息保存到数据库的逻辑
        print("Saving user data to the database...")

    def _close_database_connection(self):
        # 模拟关闭数据库连接的逻辑
        print("Closing database connection...")


# 使用示例
registration = UserRegistration("john_doe", "password123")
registration.register()

在上述示例中,我们定义了一个DatabaseError自定义异常类,用于处理与数据库相关的异常情况。

UserRegistration类中,register()方法用于用户注册操作。在该方法中,我们通过调用_save_to_database()方法来模拟将用户信息保存到数据库的过程。在_save_to_database()方法中,我们处理了可能发生的不同异常情况,并根据具体情况抛出相应的DatabaseError异常。

通过异常处理机制,我们可以在出现异常时,提供有意义的错误信息,并对不同类型的异常进行不同的处理。这有助于提高程序的稳定性和可维护性,同时使得我们能够更好地定位和解决问题。

2. 文件

2.1 文件的打开与读取

在Python中,可以使用内置的open()函数来打开文件,并使用文件对象的close()方法来关闭文件。下面是打开和关闭文件的基本操作示例:

# 打开文件
file = open("example.txt", "r")  # 打开名为 example.txt 的文件进行读取

# 读取文件内容
content = file.read()
print(content)

# 关闭文件
file.close()

上述示例中,我们使用open()函数打开名为example.txt的文件,并指定打开模式为"r",表示以只读模式打开文件。然后,我们使用文件对象的read()方法读取文件的内容并将其存储在变量content中。最后,我们使用文件对象的close()方法关闭文件。

请注意,在使用open()函数打开文件后,一定要记得使用close()方法关闭文件。这样可以确保文件资源被正确释放,防止文件句柄泄漏和资源浪费。

另外,还有一种更安全的方式是使用with语句来处理文件操作。使用with语句可以自动管理文件的打开和关闭,无需手动调用close()方法。下面是使用with语句的示例:

# 使用 with 语句打开文件
with open("example.txt", "r") as file:
    content = file.read()
    print(content)
    # 在 with 代码块内部,文件已自动关闭

# 在 with 代码块外部,文件已自动关闭

在上述示例中,使用with语句打开文件,并将文件对象赋值给变量file。在with代码块内部,我们可以执行文件操作,并无需显式调用close()方法。当with代码块结束后,文件会自动关闭,无需手动处理。这种方式更加简洁和安全,推荐使用。

2.2 路径的使用

在文件操作中,Windows和Linux对于路径的表示有一些差异。

在Windows中,路径使用反斜杠(\)作为目录分隔符,例如:C:\Users\Username\Documents\file.txt

在Linux中,路径使用正斜杠(/)作为目录分隔符,例如:/home/username/documents/file.txt

当在Python代码中使用路径时,可以根据操作系统的不同采用不同的表示方式。为了在跨平台的环境下保持代码的可移植性,建议使用os.path模块提供的函数来处理路径。这些函数会根据当前操作系统自动选择适当的路径分隔符。

下面是一个示例,展示了如何使用os.path模块处理路径:

import os

# 构建路径
path = os.path.join("C:", "Users", "Username", "Documents", "file.txt")
print(path)  # Windows: C:\Users\Username\Documents\file.txt  Linux: C:/Users/Username/Documents/file.txt

# 检查路径是否存在
exists = os.path.exists(path)
print(exists)

# 获取路径的目录名和文件名
dirname = os.path.dirname(path)
filename = os.path.basename(path)
print(dirname)  # Windows: C:\Users\Username\Documents  Linux: C:/Users/Username/Documents
print(filename)  # file.txt

关于绝对路径和相对路径的差异:

  • 绝对路径是完整的文件路径,从文件系统的根目录开始,可以唯一确定一个文件或目录。例如:C:\Users\Username\Documents\file.txt是一个绝对路径。
  • 相对路径是相对于当前工作目录的路径,不包含根目录信息。相对路径指定文件或目录相对于当前脚本所在位置的位置。例如:Documents\file.txt是相对路径,假设当前工作目录为C:\Users\Username,那么该相对路径指向C:\Users\Username\Documents\file.txt

在使用相对路径时需要注意:

  • 当前工作目录可能因操作系统、执行环境或脚本执行位置而异。可以使用os.getcwd()函数获取当前工作目录。
  • 在使用相对路径时,需要确保当前工作目录正确,并且文件或目录存在于该路径下。

为了避免不同操作系统的路径问题,可以使用os.path模块提供的函数来处理路径,它能够自动适配不同的操作系统和路径表示方式,增加代码的可移植性。

2.3 读取大文件的方式

在处理大文件时,为了避免一次性加载整个文件到内存中造成内存溢出,可以使用逐行或逐块读取文件的方式。下面介绍两种常用的方法:

(1)逐行读取文件: 逐行读取文件是一种常见且简单的方式。可以使用open()函数打开文件,然后使用readline()方法逐行读取文件内容。

with open('large_file.txt', 'r') as file:
    for line in file:
        # 处理每一行的数据
        print(line)

在这个例子中,通过open()函数打开名为large_file.txt的文件,使用for循环逐行遍历文件的内容。每次迭代,line变量将存储文件的一行数据,然后可以进行相应的处理。

(2)逐块读取文件: 当文件非常大时,逐行读取可能仍然会占用较多内存。此时可以使用逐块读取文件的方式,通过指定读取的字节数来控制内存的使用。

chunk_size = 4096  # 每次读取的字节数

with open('large_file.txt', 'r') as file:
    while True:
        data = file.read(chunk_size)
        if not data:
            break
        # 处理读取的数据块
        print(data)

在这个例子中,我们使用open()函数打开名为large_file.txt的文件,然后使用while循环不断读取指定大小的数据块,直到文件末尾。每次读取的数据块通过data变量保存,然后可以进行相应的处理。

无论是逐行读取还是逐块读取,使用with open()语句可以确保文件在使用后被正确关闭,释放资源。

以上是读取大文件的两种常见方式,根据实际需求选择适合的方法。另外,在处理大文件时,还可以考虑使用生成器函数或第三方库(如pandas)进行更高级的文件处理操作,以提高效率和灵活性。

(3)当处理大文件时,可以使用以下示例代码作为参考。该示例涵盖了文件编码设置、校验读取到的内容片段以及异常处理等细节。

def read_large_file(file_path, chunk_size=4096, encoding='utf-8'):
    try:
        with open(file_path, 'r', encoding=encoding) as file:
            while True:
                data = file.read(chunk_size)
                if not data:
                    break
                # 校验读取到的内容片段
                if validate_data(data):
                    yield data  # 使用生成器函数逐块返回数据
    except FileNotFoundError:
        print(f"File '{file_path}' not found.")
    except IOError as e:
        print(f"Error reading file '{file_path}': {str(e)}")
    except UnicodeDecodeError:
        print(f"Unable to decode file '{file_path}' using encoding '{encoding}'.")

def validate_data(data):
    # 进行内容校验的逻辑
    if 'keyword' in data:
        return True
    return False

# 使用示例
file_path = 'large_file.txt'
for chunk in read_large_file(file_path, chunk_size=8192, encoding='utf-8'):
    # 处理每个数据块
    print(chunk)

在上述示例中,read_large_file()函数用于读取大文件。函数接受文件路径、读取的块大小和文件编码作为参数。通过open()函数打开文件,并使用read()方法逐块读取数据。然后,使用validate_data()函数对每个数据块进行校验,仅当校验通过时才将数据块通过生成器函数yield返回。

在异常处理方面,代码使用了try-except块来捕获文件不存在、IO错误和Unicode解码错误等异常,并进行相应的处理和打印错误消息。

这个示例代码考虑了文件编码设置、数据校验以及异常处理等细节,帮助你更好地处理大文件,并提供了灵活性和可扩展性。

2.4 文件的写入

在Python中,可以使用多种方式进行文件写入。以下是常见的文件写入方式及示例代码:

(1)使用write()方法逐行写入文本文件:

def write_to_file(filename, data):
    with open(filename, 'w') as file:
        for line in data:
            file.write(line + '\n')

示例中的write_to_file()函数接受文件名和数据作为参数。使用open()函数打开文件,使用'w'模式表示写入模式。然后,使用write()方法逐行写入数据到文件中,并在每行末尾添加换行符。

(2)使用writelines()方法批量写入文本文件:

def write_to_file(filename, data):
    with open(filename, 'w') as file:
        file.writelines(data)

示例中的write_to_file()函数接受文件名和数据作为参数。使用open()函数打开文件,使用'w'模式表示写入模式。然后,使用writelines()方法将整个数据列表写入文件,无需逐行添加换行符。

(3)使用write()方法写入二进制文件:

def write_binary_file(filename, data):
    with open(filename, 'wb') as file:
        file.write(data)

示例中的write_binary_file()函数接受文件名和二进制数据作为参数。使用open()函数打开文件,使用'wb'模式表示以二进制写入模式打开文件。然后,使用write()方法将二进制数据写入文件。

(4)使用json模块将数据以JSON格式写入文件:

import json

def write_json_file(filename, data):
    with open(filename, 'w') as file:
        json.dump(data, file)

示例中的write_json_file()函数接受文件名和数据作为参数。使用open()函数打开文件,使用'w'模式表示写入模式。然后,使用json.dump()方法将数据以JSON格式写入文件。

(5)使用pickle模块将数据序列化后写入文件:

import pickle

def write_pickle_file(filename, data):
    with open(filename, 'wb') as file:
        pickle.dump(data, file)

示例中的write_pickle_file()函数接受文件名和数据作为参数。使用open()函数打开文件,使用'wb'模式表示以二进制写入模式打开文件。然后,使用pickle.dump()方法将数据序列化后写入文件。

2.5 文件读写的完整示例

def copy_file(source_file, destination_file):
    try:
        # 打开源文件进行读取
        with open(source_file, 'r') as source:
            # 读取源文件内容
            data = source.read()
        
        # 打开目标文件进行写入
        with open(destination_file, 'w') as destination:
            # 写入目标文件
            destination.write(data)
        
        print("文件复制完成")
    
    except FileNotFoundError:
        print("文件不存在")
    
    except PermissionError:
        print("没有权限访问文件")
    
    except IOError:
        print("文件读写错误")

# 示例调用
copy_file("source.txt", "destination.txt")

以上示例中,copy_file()函数接受源文件名和目标文件名作为参数。首先,使用open()函数以读取模式打开源文件,并使用read()方法读取文件内容到变量data中。接着,使用open()函数以写入模式打开目标文件,并使用write()方法将data写入目标文件。最后,在异常处理中捕获可能发生的错误,如文件不存在、权限错误和IO错误,并输出相应的错误信息。

注意,在实际应用中,应该根据具体需求进行异常处理和逻辑验证,例如判断源文件是否存在、目标文件是否可写等。

2.6 文件的读写模式

2.6.1 常用模式

在Python中,文件的读写模式有以下几种:

(1)读取模式:

  • 'r': 以只读模式打开文件,文件必须存在,如果文件不存在会抛出FileNotFoundError。适用于读取文件内容。
  • 'rb': 以二进制只读模式打开文件。适用于读取二进制文件,如图片、音频等。

(2)写入模式:

  • 'w': 以写入模式打开文件,如果文件已存在,会清空文件内容;如果文件不存在,会创建新文件。适用于写入新内容到文件中。
  • 'wb': 以二进制写入模式打开文件。适用于写入二进制数据到文件中。

(3)追加模式:

  • 'a': 以追加模式打开文件,如果文件已存在,写入的内容会追加到文件末尾;如果文件不存在,会创建新文件。适用于向文件追加内容。

(4)读写模式:

  • 'r+': 以读写模式打开文件,文件必须存在。适用于既要读取文件内容又要写入新内容的场景。
  • 'w+': 以读写模式打开文件,如果文件已存在,会清空文件内容;如果文件不存在,会创建新文件。适用于读取和写入文件内容的场景。

2.6.2 特殊模式

在Python中,文件读取模式还有两种变体,即tb

  • 't' 模式(文本模式):这是默认模式,表示以文本形式打开文件,用于读取和写入普通文本文件。在文本模式下,文件内容会被解码为字符串。

  • 'b' 模式(二进制模式):这是以二进制形式打开文件,用于读取和写入二进制文件,如图片、音频等。在二进制模式下,文件内容不会被解码,以字节的形式进行读取和写入。

这两种模式可以与其他读写模式(如'r''w''a'等)结合使用。

下面是示例代码:

(1)以文本模式读取文件:

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

(2)以二进制模式读取文件:

with open('image.jpg', 'rb') as file:
    data = file.read()
    # 处理二进制数据

2.7 读取文件的位置

如果你想要读取文件的位置(偏移量),可以使用seek()方法来设置文件指针的位置。seek()方法接受两个参数:偏移量和参考点。

语法:

file.seek(offset, whence)

其中,offset表示偏移量,是一个整数值。正值表示向文件末尾方向移动,负值表示向文件开头方向移动,0表示从文件开头开始。

whence表示参考点,可以使用以下值:

  • 0:参考点为文件开头,默认值。
  • 1:参考点为当前文件指针位置。
  • 2:参考点为文件末尾。

注意:只有以二进制模式打开的文件('b'模式)支持seek()方法。

下面是一个示例,演示如何使用seek()方法来读取文件的位置:

with open('file.txt', 'rb') as file:
    file.seek(10, 0)  # 从文件开头偏移10个字节
    data = file.read(5)  # 读取5个字节的数据
    print(data)

    file.seek(-5, 1)  # 从当前位置向文件开头偏移5个字节
    data = file.read(10)  # 读取10个字节的数据
    print(data)

    file.seek(-10, 2)  # 从文件末尾向文件开头偏移10个字节
    data = file.read()  # 读取剩余的数据
    print(data)

在这个示例中,我们使用了不同的偏移量和参考点,通过seek()方法在文件中移动文件指针,并读取相应的数据。

2.8 tell()方法

在 Python 的文件操作中,tell() 方法用于返回文件对象的当前位置(指针位置)。

在文本文件中,位置表示为从文件开头到当前位置的字节数(即偏移量)。在二进制文件中,位置表示为从文件开头到当前位置的字节数。

tell() 方法的语法如下:

file.tell()

2.9 文件的其他操作

在 Python 中,可以使用 os 模块和 shutil 模块来进行文件和目录相关的操作。下面是一些常见的文件和目录操作的示例代码:

(1)获取指定目录的目录结构:

import os

def get_directory_structure(directory):
    structure = {}
    for dirpath, dirnames, filenames in os.walk(directory):
        current_dir = structure
        for dirname in dirpath.split(os.path.sep):
            current_dir = current_dir.setdefault(dirname, {})
        for filename in filenames:
            current_dir.setdefault(filename, {})
    return structure

directory = '/path/to/directory'
structure = get_directory_structure(directory)
print(structure)

(2)获取当前所在的目录:

import os

current_directory = os.getcwd()
print(current_directory)

(3)切换当前所在的目录:

import os

new_directory = '/path/to/new_directory'
os.chdir(new_directory)

(4)创建目录:

import os

directory = '/path/to/new_directory'
os.mkdir(directory)

(5)删除目录:

import os

directory = '/path/to/directory'
os.rmdir(directory)

(6)删除文件:

import os

file = '/path/to/file'
os.remove(file)

(7)重命名文件或目录:

import os

old_name = '/path/to/old_name'
new_name = '/path/to/new_name'
os.rename(old_name, new_name)
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值