MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Python文本文件操作与正则表达式结合使用

2024-04-146.1k 阅读

Python文本文件操作基础

在Python中,操作文本文件是一项常见任务。使用内置的open()函数可以打开一个文本文件,该函数接受文件名和打开模式作为参数。打开模式有多种,常见的包括:

  • 'r':只读模式,这是默认模式。如果文件不存在,会抛出FileNotFoundError异常。
  • 'w':写入模式,如果文件已存在,会清空文件内容;如果文件不存在,会创建新文件。
  • 'a':追加模式,在文件末尾添加内容。如果文件不存在,会创建新文件。

例如,以只读模式打开一个文件:

try:
    file = open('example.txt', 'r')
    content = file.read()
    print(content)
    file.close()
except FileNotFoundError:
    print("文件未找到")

在上述代码中,首先尝试打开example.txt文件,如果成功打开,就读取文件内容并打印,最后关闭文件。若文件不存在,捕获FileNotFoundError异常并打印提示信息。

关闭文件很重要,因为操作系统对同时打开的文件数量有限制,并且及时关闭文件可以确保数据被正确写入磁盘。为了简化文件操作并自动处理文件关闭,可以使用with语句:

try:
    with open('example.txt', 'r') as file:
        content = file.read()
        print(content)
except FileNotFoundError:
    print("文件未找到")

with语句会在代码块结束时自动关闭文件,无需显式调用file.close()

读取文件内容除了使用read()方法读取整个文件外,还可以使用readline()方法逐行读取:

try:
    with open('example.txt', 'r') as file:
        line = file.readline()
        while line:
            print(line.strip())  # strip()方法用于去除每行末尾的换行符
            line = file.readline()
except FileNotFoundError:
    print("文件未找到")

上述代码通过readline()方法逐行读取文件内容,并使用strip()方法去除每行末尾的换行符。

readlines()方法则可以一次性读取文件的所有行,并返回一个包含每行内容的列表:

try:
    with open('example.txt', 'r') as file:
        lines = file.readlines()
        for line in lines:
            print(line.strip())
except FileNotFoundError:
    print("文件未找到")

写入文件也很简单。以写入模式打开文件后,可以使用write()方法写入内容:

with open('new_file.txt', 'w') as file:
    file.write("这是新写入的内容\n")
    file.write("这是第二行内容\n")

上述代码创建了一个新文件new_file.txt,并写入两行内容,每行末尾添加了换行符\n

在追加模式下写入文件:

with open('new_file.txt', 'a') as file:
    file.write("这是追加的内容\n")

这样就在new_file.txt文件的末尾追加了一行内容。

正则表达式基础

正则表达式是一种用于匹配和处理文本的强大工具。在Python中,通过re模块来支持正则表达式操作。

一个简单的正则表达式模式示例是匹配数字。例如,要匹配字符串中的所有数字,可以使用\d字符类,它代表任意一个数字字符(0 - 9):

import re

text = "我的电话号码是1234567890,房间号是101"
result = re.findall(r'\d+', text)
print(result)

在上述代码中,re.findall()函数用于在文本中查找所有匹配的内容。r'\d+'是正则表达式模式,r前缀表示这是一个原始字符串,在Python中,正则表达式通常使用原始字符串,以避免对反斜杠进行额外的转义。\d+表示匹配一个或多个数字字符。re.findall()函数返回一个列表,包含所有匹配的内容,运行上述代码会输出['1234567890', '101']

正则表达式中的字符类还有很多,例如:

  • \w:匹配任意一个字母、数字或下划线字符(等价于[a-zA-Z0-9_])。
  • \s:匹配任意一个空白字符(包括空格、制表符、换行符等)。
  • .:匹配除换行符之外的任意一个字符。

量词用于指定前面的字符或字符类出现的次数:

  • *:匹配前面的字符或字符类零次或多次。
  • +:匹配前面的字符或字符类一次或多次。
  • ?:匹配前面的字符或字符类零次或一次。
  • {n}:匹配前面的字符或字符类恰好n次。
  • {n,}:匹配前面的字符或字符类至少n次。
  • {n,m}:匹配前面的字符或字符类至少n次,至多m次。

例如,要匹配邮箱地址,可以使用如下正则表达式模式:r'\w+@\w+\.\w+'。这个模式的含义是:首先匹配一个或多个字母、数字或下划线字符(\w+),接着匹配@符号,然后再匹配一个或多个字母、数字或下划线字符,最后匹配一个点号和一个或多个字母、数字或下划线字符。示例代码如下:

import re

text = "我的邮箱是example@example.com,他的邮箱是test@test.cn"
result = re.findall(r'\w+@\w+\.\w+', text)
print(result)

运行上述代码会输出['example@example.com', 'test@test.cn']

分组在正则表达式中非常有用,它可以将多个字符组合成一个单元,并可以对这个单元应用量词或进行单独的匹配操作。在正则表达式中,使用圆括号()来表示分组。例如,要匹配日期格式YYYY - MM - DD,可以使用模式r'(\d{4})-(\d{2})-(\d{2})'

import re

text = "今天的日期是2023 - 10 - 05,明天是2023 - 10 - 06"
result = re.findall(r'(\d{4})-(\d{2})-(\d{2})', text)
print(result)

这里的(\d{4})(\d{2})(\d{2})分别是三个分组,re.findall()函数返回的结果是一个列表,列表中的每个元素是一个元组,元组中的元素分别对应各个分组匹配到的内容,运行上述代码会输出[('2023', '10', '05'), ('2023', '10', '06')]

Python文本文件操作与正则表达式结合应用

在实际应用中,常常需要从文本文件中提取特定格式的数据,这就需要将文本文件操作与正则表达式结合使用。

假设我们有一个日志文件log.txt,内容如下:

2023 - 10 - 01 12:00:00 INFO 系统启动
2023 - 10 - 01 12:05:00 ERROR 数据库连接失败
2023 - 10 - 02 08:30:00 INFO 服务正常运行

我们想要提取日志中的日期、时间和日志级别。可以使用如下代码:

import re

try:
    with open('log.txt', 'r') as file:
        for line in file.readlines():
            pattern = r'(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) (\w+)'
            match = re.search(pattern, line)
            if match:
                date = match.group(1)
                time = match.group(2)
                level = match.group(3)
                print(f"日期: {date}, 时间: {time}, 日志级别: {level}")
except FileNotFoundError:
    print("文件未找到")

在上述代码中,首先使用open()函数以只读模式打开log.txt文件,并逐行读取。对于每一行,使用re.search()函数在该行中查找匹配的内容。re.search()函数会在字符串中查找第一个匹配的位置,如果找到匹配项,返回一个匹配对象,否则返回None。这里的正则表达式模式r'(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) (\w+)'使用了三个分组,分别用于匹配日期、时间和日志级别。如果找到匹配项,通过match.group(1)match.group(2)match.group(3)分别获取各个分组匹配到的内容,并打印出来。

再比如,有一个包含大量邮件地址的文本文件emails.txt,我们要验证并提取出有效的邮件地址,并将其写入到另一个文件valid_emails.txt中。假设邮件地址的格式要求为:用户名部分可以包含字母、数字、下划线、点号,域名部分可以包含字母、数字、点号,且域名至少包含一个点号。代码如下:

import re

try:
    with open('emails.txt', 'r') as infile, open('valid_emails.txt', 'w') as outfile:
        for line in infile.readlines():
            pattern = r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'
            if re.match(pattern, line.strip()):
                outfile.write(line)
except FileNotFoundError:
    print("文件未找到")

在上述代码中,re.match()函数用于从字符串的开头开始匹配正则表达式。^表示字符串的开头,$表示字符串的结尾,这样可以确保整个字符串都匹配指定的邮件地址格式。对于emails.txt中的每一行,先使用strip()方法去除行两端的空白字符,然后使用re.match()函数进行匹配。如果匹配成功,说明这是一个有效的邮件地址,将其写入到valid_emails.txt文件中。

有时候,需要在文本文件中替换符合特定正则表达式模式的内容。例如,有一个文本文件text.txt,内容如下:

原文本:今天是2023年10月5日,天气不错。

我们要将日期格式从YYYY年MM月DD日替换为YYYY - MM - DD。代码如下:

import re

try:
    with open('text.txt', 'r') as file:
        content = file.read()
        new_content = re.sub(r'(\d{4})年(\d{2})月(\d{2})日', r'\1-\2-\3', content)
    with open('text.txt', 'w') as file:
        file.write(new_content)
except FileNotFoundError:
    print("文件未找到")

在上述代码中,re.sub()函数用于在字符串中替换匹配正则表达式的内容。第一个参数是正则表达式模式r'(\d{4})年(\d{2})月(\d{2})日',其中使用了三个分组分别匹配年份、月份和日期。第二个参数r'\1-\2-\3'是替换后的字符串,\1\2\3分别表示第一个、第二个和第三个分组匹配到的内容。首先读取text.txt文件的内容,进行替换操作后,再将新的内容写回到text.txt文件中。

处理复杂文本结构与多行匹配

在处理一些复杂的文本文件时,可能会遇到需要跨行匹配的情况。例如,有一个HTML文件example.html,内容如下:

<html>
<head>
    <title>示例页面</title>
</head>
<body>
    <p>这是一段文本。</p>
    <p>这是另一段文本,其中包含一个链接:<a href="https://example.com">点击这里</a>。</p>
</body>
</html>

假设我们要提取HTML文件中的所有链接。由于链接可能分布在不同的行,单纯逐行匹配可能会遗漏一些情况。可以使用re.DOTALL标志来使.字符匹配包括换行符在内的所有字符。代码如下:

import re

try:
    with open('example.html', 'r') as file:
        content = file.read()
        pattern = r'<a href="([^"]+)">'
        links = re.findall(pattern, content, re.DOTALL)
        for link in links:
            print(link)
except FileNotFoundError:
    print("文件未找到")

在上述代码中,re.findall()函数的第三个参数re.DOTALL使得.字符可以匹配换行符。正则表达式模式r'<a href="([^"]+)">'用于匹配<a href="链接地址">这样的格式,并通过分组提取出链接地址。

对于一些更复杂的文本结构,如XML文件,也可以使用正则表达式结合文本文件操作来提取信息。假设我们有一个XML文件example.xml,内容如下:

<bookstore>
    <book category="fiction">
        <title lang="en">哈利·波特与魔法石</title>
        <author>J.K.罗琳</author>
        <price>29.99</price>
    </book>
    <book category="non-fiction">
        <title lang="en">人类简史</title>
        <author>尤瓦尔·赫拉利</author>
        <price>39.99</price>
    </book>
</bookstore>

要提取出每本书的标题和价格,可以使用如下代码:

import re

try:
    with open('example.xml', 'r') as file:
        content = file.read()
        pattern = r'<book.*?><title lang="en">(.*?)</title><author>(.*?)</author><price>(.*?)</price></book>'
        books = re.findall(pattern, content, re.DOTALL)
        for book in books:
            title = book[0]
            author = book[1]
            price = book[2]
            print(f"标题: {title}, 作者: {author}, 价格: {price}")
except FileNotFoundError:
    print("文件未找到")

在上述代码中,正则表达式模式r'<book.*?><title lang="en">(.*?)</title><author>(.*?)</author><price>(.*?)</price></book>'使用了.*?这种非贪婪匹配方式,以确保能够正确匹配每本书的信息,即使在XML文件格式不太规范(如标签之间可能有多余的空白或换行)的情况下也能准确提取。通过re.findall()函数结合re.DOTALL标志,获取所有匹配的书籍信息,并进行打印。

性能优化与注意事项

在使用文本文件操作与正则表达式结合时,性能是一个需要考虑的因素。正则表达式的复杂度对性能有很大影响。例如,使用过于复杂的正则表达式,如包含大量嵌套的分组和量词,可能会导致匹配速度变慢。尽量使用简单的正则表达式来完成任务,如果可能,预先编译正则表达式可以提高性能。在Python中,可以使用re.compile()函数来编译正则表达式,示例如下:

import re

pattern = re.compile(r'\d+')
text = "123 abc 456 def"
result = pattern.findall(text)
print(result)

编译后的正则表达式对象pattern在多次使用时会比直接使用re.findall(r'\d+', text)更高效,尤其是在循环中多次匹配相同模式的情况下。

另外,在处理大文件时,逐行读取并处理比一次性读取整个文件内容到内存中更节省内存。例如,在处理一个非常大的日志文件时,逐行读取并匹配正则表达式:

import re

pattern = re.compile(r'(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) (\w+)')
try:
    with open('large_log.txt', 'r') as file:
        for line in file:
            match = pattern.search(line)
            if match:
                date = match.group(1)
                time = match.group(2)
                level = match.group(3)
                print(f"日期: {date}, 时间: {time}, 日志级别: {level}")
except FileNotFoundError:
    print("文件未找到")

这样可以避免一次性将大文件的所有内容读入内存,从而减少内存消耗。

同时,在使用正则表达式时要注意边界条件。例如,在匹配邮件地址时,要考虑到各种合法和非法的情况,如邮箱地址不能以点号开头或结尾,域名部分不能全是数字等。通过完善正则表达式模式,可以提高匹配的准确性,避免误匹配或漏匹配。

在处理文本文件时,还要注意文件编码问题。不同的文本文件可能采用不同的编码方式,如UTF - 8、GBK等。在打开文件时,可以指定编码方式,例如:

try:
    with open('file.txt', 'r', encoding='utf - 8') as file:
        content = file.read()
        # 后续处理
except FileNotFoundError:
    print("文件未找到")
except UnicodeDecodeError:
    print("编码解码错误,请检查文件编码")

如果不指定编码,Python会根据系统默认编码来读取文件,这可能会导致在处理非默认编码的文件时出现UnicodeDecodeError异常。

在实际应用中,还可以结合其他Python库来进一步增强文本处理能力。例如,pandas库在处理结构化文本数据(如CSV文件)方面非常强大,它可以与正则表达式结合,更方便地进行数据清洗和分析。假设我们有一个CSV文件data.csv,内容如下:

name,age,email
John,25,John@example.com
Jane,30,Jane@example.net

我们可以使用pandas库读取CSV文件,并使用正则表达式验证邮箱地址:

import pandas as pd
import re

pattern = re.compile(r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$')

data = pd.read_csv('data.csv')
for index, row in data.iterrows():
    email = row['email']
    if not pattern.match(email):
        print(f"无效的邮箱地址: {email}")

在上述代码中,首先使用pandasread_csv()函数读取CSV文件内容到一个DataFrame对象中。然后通过iterrows()方法逐行遍历DataFrame,获取每一行中的邮箱地址,并使用预先编译的正则表达式进行验证。如果邮箱地址无效,打印提示信息。

通过合理结合文本文件操作、正则表达式以及其他相关库,可以高效准确地处理各种文本数据,满足不同的业务需求。无论是数据提取、清洗还是分析,这些技术的综合应用都能发挥重要作用。