前言
在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
库中的 search
、findall
、compile
和 with_pattern
方法的用法。
1. search
方法
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
库用法请翻阅官方文档...
最后
今天的分享就到这里。如果觉得不错,点赞
,关注
安排起来吧。