parse库,一个优雅的python库

news2024/11/15 23:35:25

前言

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

name = "Haige"
age = "18"
print(f"{name} is {age} years old.")

# Haige is 18 years old.

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

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

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

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

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

Github地址:https://github.com/r1chardj0n3s/parse

初体验

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

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

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

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")

输出结果:

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

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

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

pip install parse

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

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")

输出结果:

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

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

通过对比可以看出,使用parse库进行字符串解析相对更加简洁、清晰,并且不容易出错。
而使用正则表达式虽然也可以完成相同的任务,但是需要编写更长的模式,并且容易出现错误,没有一丝美感可言

parse库的基本用法

parse 的结果

parse的结果只有两种结果:

  • 没有匹配上,parse的值为None
  • 如果匹配上,parse的值则为Result实例
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("匹配失败")

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

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 方法的用法。

1. search 方法

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

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("未找到匹配项")

输出结果:

Fruit: apple
Price: 2
2. findall 方法

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

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("未找到匹配项")

输出结果:

Price 1: 2.50
Price 2: 3.00
Price 3: 4.25
3. compile 方法

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

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("未找到匹配项")

输出结果:

Fruit: apple
Price: 2.50
4. with_pattern 方法

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

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)

输出结果:

xx@xxx.com
None

捕获组:

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) {}>

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

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,) {}>

匹配并类型转换

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

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("未找到匹配项")

输出结果:

Number of apples: 5
Type of count: <class 'int'>

匹配时间:

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

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

特殊对齐

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))

输出结果:

<Result ('world   ',) {}>
<Result ('    world',) {}>
<Result ('world',) {}>
<Result ('world',) {}>
<Result ('world',) {}>

大小写敏感开关

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

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')]),大小写不敏感匹配成功

匹配字符数

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

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'))

输出结果:

<Result ('look', 'at that') {}>
<Result ('look at ', 'that') {}>
<Result (4, 40) {}>

三个重要属性

  • fixed:利用位置提取的匿名字段的元组。
  • named:存放有命名的字段的字典。
  • spans:存放匹配到字段的位置。
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)

输出结果:

('male',)
{'name': 'Jack', 'age': 27}
{'name': (5, 9), 'age': (11, 13), 0: (25, 29)}

自定义类型转换

from parse import parse


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


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

输出结果:

<Result ('HELLO HAIGE',) {}>

使用场景

解析nginx日志

#!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')

输出结果

                                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]

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

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库用法请翻阅官方文档…

最后

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

tkinter绘制组件(41)——菜单按钮

tkinter绘制组件&#xff08;41&#xff09;——菜单按钮 引言布局函数结构按钮部分菜单显示完整代码函数 效果测试代码最终效果 github项目pip下载结语 引言 TinUI5的新控件&#xff0c;菜单按钮&#xff0c;menubutton。 这是一个与TinUI菜单&#xff08;menubar&#xff0…

【C++】基础知识讲解(引用、内联、auto,基于范围for循环)

&#x1f308;个人主页&#xff1a;秦jh__https://blog.csdn.net/qinjh_?spm1010.2135.3001.5343&#x1f525; 系列专栏&#xff1a;http://t.csdnimg.cn/eCa5z 目录 引用 概念 特性 使用场景 作参数 作返回值 传值、传引用效率比较 引用和指针的区别 内联函数 概念…

excel 导出 The maximum length of cell contents (text) is 32767 characters

导出excel报错。错误日志提示&#xff1a;:The maximum length of cell contents (text) is 32767 characters 排查后&#xff0c;发现poi有单元格最大长度校验&#xff0c;超过32767会报错。 解决方案&#xff1a; 通过java反射机制&#xff0c;设置单元格最大校验限制为Int…

Skywalking 应用笔记

概念 Skywalking是一款分布式的系统 性能监视工具&#xff0c;专为微服务、云原生架构和基于容器(Docker、K8s、Mesos)架构而设计。SkyWalking是一款 观察性的分析平台和应用性能管理系统&#xff0c;提供了 分布式追踪、性能指标分析、应用服务依赖分析、可视化一体化等解决方…

CV | SAM在医学影像上的模型调研【20240207更新版】

本文主要是SAM&#xff08;Segment Anything&#xff09;在医学影像上的数据集&#xff0c;模型及评估方法调研【持续更新】~ 1.开源数据集 可参考这篇【数据集 | 基于计算机视觉的医学影像处理数据集_CSDN博客】 2.算法模型 2023.04_SAM 论文&#xff1a;2018.08.05v_Segm…

Win32 SDK Gui编程系列之--弹出式菜单

1.弹出式菜单 例如,在命令提示窗口中点击鼠标右键,会出现如下图所示的弹出菜单(下拉菜单)。 这种弹出式菜单的实现很简单。不创建菜单栏,用CreatePopupMenu函数创建的菜单是最顶端的菜单就可以了。 菜单的显示使用TrackPopupMenu函数进行。 例如,点击鼠标右键显示弹出…

尚硅谷 Java 基础实战—Bank 项目—实验题目 3

实验题目 修改 withdraw 方法以返回一个布尔值&#xff0c;指示交易是否成功。 实验目的 使用有返回值的方法。 提示 修改 Account 类 修改 deposit 方法返回 true&#xff08;意味所有存款是成功的&#xff09;。修改 withdraw 方法来检查提款数目是否大于余额。如果amt小…

十七、vben合并行后操作按钮如何合并

上期我们说了如何在table内部合并行,行内的内容都是字符串,那么如果是多个操作按钮呢,他们是如何合并的,事件是怎么触发的,怎么写呢。 先看效果图 数据上也是和上期一样有9条信息。 下面来看一下我们的具体实现 一、在template里面写table <BasicTable:showIndexCol…

【网络技术】【Kali Linux】Nmap 嗅探(一)简单扫描

一、实验环境 本次实验进行简单的Nmap扫描&#xff0c;实验使用 Kali Linux 虚拟机和 Ubuntu Linux 虚拟机完成&#xff0c;主机操作系统为 Windows 11&#xff0c;虚拟化平台选择 Oracle VM VirtualBox&#xff0c;如下图所示。 二、实验步骤 1、相关配置 Kali Linux 虚拟机…

【python】python实现代码雨【附源码】

欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 系列文章 1新年烟花代码https://blog.csdn.net/m0_73367097/article/details/1354817792爱心代码https://blog.csdn.net/m0_73367097/article/details/136017032 一、效果图&#xff1a; 二、准备工作 &#xff08;1…

第二证券:股市的国家队是谁?股市国家队包括哪些机构?

在a股商场上&#xff0c;投资者大致能够分为散户、游资、主力、组织、国家队这几大类&#xff0c;那么&#xff0c;股市的国家队是谁&#xff1f;股市国家队包含哪些组织&#xff1f; 国家队主要是指以下五大类&#xff1a; 1、中心汇金 中心汇金的全称为中心汇金投资有限责…

【STM32F103】PWM驱动舵机(SG90MG995)

PWM 关于如何发出PWM可以参考我之前的文章。 【STM32F103】TIM定时器&PWM-CSDN博客 SG90&MG995 以这两款舵机为例是因为我手上碰巧只有这两款舵机。不过实际上舵机的操作基本上差不了多少&#xff0c;基本上都是给频率为50Hz的PWM&#xff0c;然后就可以让舵机旋转…

详解C++类和对象(中(类的6个默认成员函数))

文章目录 写在前面1. 类的6个默认成员函数2. 构造函数2.1 构造函数的引入2.1 构造函数的特性 3. 析构函数3.1 析构函数的引入3.2 析构函数的特性 4. 拷贝构造函数4.1 拷贝构造函数概念4.2 拷贝构造函数的特性4.3 拷贝构造函数典型调用场景 5. 赋值运算符重载5.1 运算符重载5.2 …

Javaweb之SpringBootWeb案例之登录校验功能的详细解析

2. 登录校验 2.1 问题分析 我们已经完成了基础登录功能的开发与测试&#xff0c;在我们登录成功后就可以进入到后台管理系统中进行数据的操作。 但是当我们在浏览器中新的页面上输入地址&#xff1a;http://localhost:9528/#/system/dept&#xff0c;发现没有登录仍然可以进…

寻迹模块——红外循迹模式使用介绍

目录 循迹模式——红外循迹模式使用介绍 红外循迹模块介绍 接线 循迹小车原理 安装与接线 实验程序 实验效果 循迹模式——红外循迹模式使用介绍 实验效果&#xff1a; 寻迹模块-CSDN直播 红外循迹模块介绍 传感器的红外发射二极管不断发射红外线&#xff0c;当发射出…

RabbitMQ-5.消费者的可靠性

消费者的可靠性 5.消费者的可靠性5.1.消费者确认机制5.2.失败重试机制5.3.失败处理策略5.4.业务幂等性5.4.1.唯一消息ID5.4.2.业务判断 5.5.兜底方案 5.消费者的可靠性 当RabbitMQ向消费者投递消息以后&#xff0c;需要知道消费者的处理状态如何。因为消息投递给消费者并不代表…

jvm几个常见面试题整理

1. Full GC触发机制有如下5种情况。 (1)调用System.gc()时&#xff0c;系统建议执行Full GC&#xff0c;但是不必然执行。(2)老年代空间不足。(3)方法区空间不足。(4)老年代的最大可用连续空间小于历次晋升到老年代对象的平均大小就会进行Full GC。(5)由Eden区、S0(From)区向S…

前端vite+vue3——自动化配置路由布局

文章目录 ⭐前言&#x1f496;vue3系列文章 ⭐ 自动化配置路由&#x1f496;引入vite版本自定义目录映射&#x1f496;自动化读取文件下的路由&#x1f496;main入口加载路由&#x1f496;入口app.vue配置&#x1f496;layout基础布局配置&#x1f496;效果 ⭐总结⭐结束 ⭐前言…

python实现中国剩余定理

中国剩余定理又称孙子定理&#xff0c;是数论中一个重要定理。最早可见于我国的数学著作《孙子算经》卷下“物不知数”问题&#xff0c;原文如下&#xff1a; 有物不知其数&#xff0c;三三数之剩二&#xff0c;五五数之剩三&#xff0c;七七数之剩二。问物几何&#xff1f;即…

车载网络测试 - 总线基础 - CAN总线负载计算

我想做过CAN总线测试的都有遇到过拉高总线负载相关的测试&#xff0c;这个时候我们一般都会通过增加报文的数量或者减小报文的周期来实现&#xff0c;但是CAN总线上的负载到底是如何计算的呢&#xff1f;我想很多人都会有这个疑问吧&#xff0c;那么今天我们一起来看下如何计算…