parse库,一个优雅而神奇的python库

前言

Python中,format方法和f-strings是两种常用的字符串插值方法。

python 复制代码
name = "Haige"
age = "18"
print(f"{name} is {age} years old.")

# Haige is 18 years old.

而如果是要从字符串中提取期望的值呢?相信很多人的第一或第二想法是使用正则表达式。

熟悉正则表达式的人都明白,学习起来并不困难,写起来也相对容易。

然而,正则表达式几乎不具备可读性,维护起来确实令人头痛。别以为你写的那段正则表达式可以轻易驾驭它,过了一段时间你可能都无法认识它了。

可以毫不夸张地说,对于许多人来说,正则表达式是一种痛苦的经历。

今天,我要介绍给你一个解放你的好工具,让你远离正则表达式的噩梦,那就是 Python 中一个"鲜为人知"的库------parse

Github地址:github.com/r1chardj0n3...

初体验

假设我们有一组文本,其中包含了一些格式化的信息,比如日期、时间和事件。我们想要从这些文本中提取出这些信息。

python 复制代码
text = "Event: Meeting Date: 2023-01-15 Time: 14:30 Location: Conference Room"

如果是你,你会怎么做呢?也许上来就是正则一把梭了。就像这样:

python 复制代码
import re

text = "Event: Meeting Date: 2023-01-15 Time: 14:30 Location: Conference Room"

# 定义正则表达式模式
pattern = r"Event: (.+) Date: (\d{4}-\d{2}-\d{2}) Time: (\d{2}:\d{2}) Location: (.+)"

# 匹配文本
match = re.match(pattern, text)

if match:
    event = match.group(1)
    date = match.group(2)
    time = match.group(3)
    location = match.group(4)
    print("Event:", event)
    print("Date:", date)
    print("Time:", time)
    print("Location:", location)
else:
    print("Failed to match the text")

输出结果:

shell 复制代码
Event: Meeting
Date: 2023-01-15
Time: 14:30
Location: Conference Room

不过,也许你该试试parse库。

首先,我们需要安装 parse 库,可以通过 pip 进行安装:

复制代码
pip install parse

我们想要从这个文本中提取出事件名称、日期、时间和地点。下面是使用 parse 库的方法:

python 复制代码
from parse import parse

# 定义模板
template = "Event: {} Date: {} Time: {} Location: {}"

# 解析文本
result = parse(template, text)

if result:
    event, date, time, location = result
    print("Event:", event)
    print("Date:", date)
    print("Time:", time)
    print("Location:", location)
else:
    print("Failed to parse the text")

输出结果:

shell 复制代码
Event: Meeting
Date: 2023-01-15
Time: 14:30
Location: Conference Room

在这个示例中,我们首先定义了一个模板,模板中包含了我们要提取的信息的格式。然后,我们使用 parse 函数解析文本,如果解析成功,返回的结果是一个元组,包含了提取出的信息。最后,我们成功地从文本中提取出了事件名称、日期、时间和地点。

通过对比可以看出,使用parse库进行字符串解析相对更加简洁、清晰,并且不容易出错。

而使用正则表达式虽然也可以完成相同的任务,但是需要编写更长的模式,并且容易出现错误,没有一丝美感可言

parse库的基本用法

parse 的结果

parse的结果只有两种结果:

  • 没有匹配上,parse的值为None
  • 如果匹配上,parse的值则为Result实例
python 复制代码
from parse import parse

# 示例字符串
log_string = '192.168.0.1 - - [05/Feb/2024:12:30:45 +0800] "GET /index.html HTTP/1.1" 200 1234'

# 定义解析模式
pattern = '{ip} - - [{timestamp}] "{method} {url}" {status_code} {response_size}'

# 解析字符串
result = parse(pattern, log_string)

# 输出解析结果
if result:
    print("IP:", result['ip'])
    print("Timestamp:", result['timestamp'])
    print("Method:", result['method'])
    print("URL:", result['url'])
    print("Status Code:", result['status_code'])
    print("Response Size:", result['response_size'])
else:
    print("匹配失败")

如果我们将正确格式的日志字符串作为输入,将会得到匹配成功的结果:

shell 复制代码
IP: 192.168.0.1
Timestamp: 05/Feb/2024:12:30:45 +0800
Method: GET
URL: /index.html
Status Code: 200
Response Size: 1234

常用方法

让我来逐个解释并举例说明 parse 库中的 searchfindallcompilewith_pattern 方法的用法。

search 方法用于在字符串中搜索与指定模式匹配的第一个结果,并返回解析结果。

python 复制代码
from parse import search

# 示例字符串
text = "The price of the apple is $2.50."

# 定义解析模式
pattern = "The price of the {fruit} is ${price}."

# 使用 search 方法解析字符串
result = search(pattern, text)

# 访问解析结果
if result:
    print("Fruit:", result['fruit'])
    print("Price:", result['price'])
else:
    print("未找到匹配项")

输出结果:

shell 复制代码
Fruit: apple
Price: 2

2. findall 方法

findall 方法用于在字符串中搜索所有与指定模式匹配的结果,并返回解析结果列表。

python 复制代码
from parse import findall

# 示例字符串
text = "The prices are $2.50, $3.00, and $4.25."

# 定义解析模式
pattern = "${price:.2f}"  # 使用 ":.2f" 匹配包含两位小数的浮点数

# 使用 findall 方法解析字符串
results = findall(pattern, text)

# 访问解析结果
if results:
    for idx, price in enumerate(results, start=1):
        # 将小数部分格式化为两位
        formatted_price = "{:.2f}".format(price['price'])
        print(f"Price {idx}: {formatted_price}")
else:
    print("未找到匹配项")

输出结果:

shell 复制代码
Price 1: 2.50
Price 2: 3.00
Price 3: 4.25

3. compile 方法

compile 方法用于将解析模式编译为可重复使用的解析器对象。

python 复制代码
from parse import compile

# 定义解析模式
pattern = "The price of the {fruit} is ${price}."

# 编译解析模式
parser = compile(pattern)

# 使用编译后的解析器对象解析字符串
result = parser.parse("The price of the apple is $2.50.")

# 访问解析结果
if result:
    print("Fruit:", result['fruit'])
    print("Price:", result['price'])
else:
    print("未找到匹配项")

输出结果:

shell 复制代码
Fruit: apple
Price: 2.50

4. with_pattern 方法

with_pattern 方法用于绑定解析模式与要解析的字符串,并返回一个解析结果对象。

python 复制代码
from parse import Parser, with_pattern


@with_pattern(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b')
def email(text: str) -> str:
    return text


compiler = Parser("my email address is {email:Email}", dict(Email=email))

legal_result = compiler.parse("my email address is xx@xxx.com")  # legal email
illegal_result = compiler.parse("my email address is xx@xx")     # illegal email

print(legal_result["email"])
print(illegal_result)

输出结果:

shell 复制代码
xx@xxx.com
None

捕获组:

python 复制代码
from parse import *


@with_pattern(r'((\d+))', regex_group_count=2)
def parse_number2(text):
    return int(text)


obj = parse('Answer: {:Number2} {:Number2}', 'Answer: 42 43', dict(Number2=parse_number2))
print(obj)  # <Result (42, 43) {}>

将输入的文本转换为布尔值:

python 复制代码
from parse import *


yesno_mapping = {
    "yes":  True,   "no":    False,
    "on":   True,   "off":   False,
    "true": True,   "false": False,
}


@with_pattern(r"|".join(yesno_mapping))
def parse_yesno(text):
    return yesno_mapping[text.lower()]


obj = parse('Answer: {:bool}', 'Answer: yes', dict(bool=parse_yesno))
print(obj)  # <Result (True,) {}>

obj2 = parse('Answer: {:bool}', 'Answer: off', dict(bool=parse_yesno))
print(obj2)  # <Result (False,) {}>

匹配并类型转换

有的时候,我们希望提取的时候就按照我们的类型进行转换。

python 复制代码
from parse import Parser

# 创建解析器对象并指定解析模式
parser = Parser("I have {count:d} apples", {})

# 示例字符串
text = "I have 5 apples"

# 使用解析器对象解析字符串
result = parser.parse(text)

# 访问解析结果
if result:
    count = result['count']
    print("Number of apples:", count)
    print("Type of count:", type(count))
else:
    print("未找到匹配项")

输出结果:

shell 复制代码
Number of apples: 5
Type of count: <class 'int'>

匹配时间:

python 复制代码
from parse import parse


datetime = parse("{:%Y-%m-%d %H:%M:%S}", "2023-11-23 12:56:47")
print(datetime)  # <Result (datetime.datetime(2023, 11, 23, 12, 56, 47),) {}>
print(datetime[0])  # 2023-11-23 12:56:47

更多类型请参考官方文档:

特殊对齐

python 复制代码
from parse import *

text = "hello     world    , hello python"

# 右对齐
print(parse('hello {:>} , hello python', text))
# 左对齐
print(parse('hello {:<} , hello python', text))
# 居中对齐
print(parse('hello {:^} , hello python', text))
print(parse('hello{:^} , hello python', text))

输出结果:

shell 复制代码
<Result ('world   ',) {}>
<Result ('    world',) {}>
<Result ('world',) {}>
<Result ('world',) {}>
<Result ('world',) {}>

大小写敏感开关

parse库默认是不区分大小写的。如果需要开启大小写敏感模式,可以通过设置case_sensitive参数为True来实现。

python 复制代码
from parse import parse

# 原始字符串
file_name = "document.TXT"

# 解析文件名,大小写敏感
result_sensitive = parse("{name}.txt", file_name, case_sensitive=True)
print(result_sensitive)  # 输出为 None,因为大小写不匹配

# 解析文件名,大小写不敏感
result_insensitive = parse("{name}.txt", file_name, case_sensitive=False)
print(result_insensitive)  # 输出为 ParseResult([('name', 'document')]),大小写不敏感匹配成功

匹配字符数

宽度和精度可用于限制输入匹配文本的大小。宽度指定最小尺寸,精度指定最大尺寸。例如:

python 复制代码
from parse import parse

parse('{:.2}{:.2}', 'look')  # 指定精度

print(parse('{:4}{:4}', 'look at that'))  # 指定宽度

print(parse('{:4}{:.4}', 'look at that'))  # 同时指定

print(parse('{:2d}{:2d}', '0440'))

输出结果:

shell 复制代码
<Result ('look', 'at that') {}>
<Result ('look at ', 'that') {}>
<Result (4, 40) {}>

三个重要属性

  • fixed:利用位置提取的匿名字段的元组。
  • named:存放有命名的字段的字典。
  • spans:存放匹配到字段的位置。
python 复制代码
from parse import parse

profile = parse("I am {name}, {age:d} years old, {}", "I am Jack, 27 years old, male")
print(profile.fixed)

print(profile.named)

print(profile.spans)

输出结果:

shell 复制代码
('male',)
{'name': 'Jack', 'age': 27}
{'name': (5, 9), 'age': (11, 13), 0: (25, 29)}

自定义类型转换

python 复制代码
from parse import parse


def custom_upper(string):
    return string.upper() + " HAIGE"


print(parse('{:my_upper} world', 'hello world', dict(my_upper=custom_upper)))

输出结果:

shell 复制代码
<Result ('HELLO HAIGE',) {}>

使用场景

解析nginx日志

python 复制代码
#!usr/bin/env python
# -*- coding:utf-8 _*-
# __author__:lianhaifeng
# __time__:2024/2/7 20:02
from parse import parse
import json
import pandas as pd
from typing import List


def parse_nginx_log(log_lines):
    template = '{ip} - - [{timestamp}] "{method} {path} HTTP/{http_version}" {status_code} {response_size} "{user_agent}"'

    data = []

    for log_line in log_lines:
        result = parse(template, log_line)

        if result:
            data.append({
                'ip': result['ip'],
                'timestamp': result['timestamp'],
                'method': result['method'],
                'path': result['path'],
                'http_version': result['http_version'],
                'status_code': int(result['status_code']),
                'response_size': int(result['response_size']),
                'user_agent': result['user_agent']
            })
    return data


def build_dataframe(records: List[dict]) -> pd.DataFrame:
    result: pd.DataFrame = pd.DataFrame.from_records(records, index='ip')
    return result


nginx_log = [
    '127.0.0.1 - - [01/Jan/2022:12:00:00 +0000] "GET /index.html HTTP/1.1" 200 1234 "-"',
    '127.0.0.1 - - [01/Jan/2022:16:00:00 +0000] "GET /index.html HTTP/1.1" 200 1234 "-"',
    '192.168.1.5 - - [01/Jan/2023:12:03:00 +0000] "GET /index3.html HTTP/1.1" 200 1236 "-"',
    '192.168.18.36 - - [01/Jan/2024:11:23:00 +0000] "GET /index2.html HTTP/1.1" 200 3234 "-"'
]

parsed_log = parse_nginx_log(nginx_log)

if parsed_log:
    json_log = json.dumps(parsed_log)
    # print(json_log)
    print(build_dataframe(parsed_log))
else:
    print('Failed to parse the log line')

输出结果

shell 复制代码
                                timestamp method  ... response_size user_agent
ip                                                ...                         
127.0.0.1      01/Jan/2022:12:00:00 +0000    GET  ...          1234          -
127.0.0.1      01/Jan/2022:16:00:00 +0000    GET  ...          1234          -
192.168.1.5    01/Jan/2023:12:03:00 +0000    GET  ...          1236          -
192.168.18.36  01/Jan/2024:11:23:00 +0000    GET  ...          3234          -

[4 rows x 7 columns]

解析配置文件中的键值对:

python 复制代码
from parse import parse

# 定义配置文件模板
template = "{key}={value}"

# 解析配置文件行
result = parse(template, "debug=True")

if result:
    key = result['key']
    value = result['value']
    print(f"Key: {key}, Value: {value}")  # Key: debug, Value: True

小结

在字符串解析处理中,parse库提供了极大的便利。相较于使用正则表达式 (re),parse简化了模式的定义和匹配过程,极大地提高了开发效率。

在一些简单的场景中,使用parse可比使用re去写正则表达式高出几个level。使用parse编写的代码富有美感,可读性极高,后期维护起来也毫无压力。

综上所述,强烈推荐您在Python开发中使用parse库,它能够让您的代码更加优雅、高效、易读易维护。

更多parse库用法请翻阅官方文档...

最后

今天的分享就到这里。如果觉得不错,点赞关注安排起来吧。

相关推荐
苏三说技术16 小时前
Claude Code从失控到起飞,只用了这些技巧
后端
aqi0016 小时前
15天学会AI应用开发(七)有了大模型为什么还要引入RAG
人工智能·python·大模型·ai编程·ai应用
长栎17 小时前
写 for 循环写了十年,你却从没用过迭代器模式最狠的那一面
后端
LiaCode17 小时前
Redis 在生产项目的使用
前端·后端
用户5598224812217 小时前
Docker Compose Down 导致容器数据误删——ext4 日志恢复全记录
后端
LiaCode17 小时前
一天学完 redis 的爽翻版核心知识总结
前端·后端
大刚测试开发实战17 小时前
如何内网穿透访问本地私有化部署的TestHub
前端·后端·github
xiaodaoluanzha17 小时前
迄今為止,最簡單的編程語言 Nolang
前端·后端
Csvn17 小时前
Docker 容器管理入门 — 从镜像到容器编排
后端