太好用了,这个更人性化的正则库 -- Humre
我先说个场景哈,特别日常那种。
前几天晚上九点多,我在公司楼下等外卖,手机上远程连着服务器,改一个小脚本,本来就想写个简单的日志过滤:把某个接口的慢请求抓出来。结果一打开那段老代码,第一眼看到这么个玩意儿:
import re
pattern = re.compile(
r'^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\]\s+'
r'(INFO|WARN|ERROR)\s+user=([\w.-]+)\s+ip=([\d.]+)\s+cost=(\d+)ms$'
)
我盯着那一坨 \d{2}、\s+、[\w.-],脑子里只有一个声音: “我当初写这坨的时候,脑回路是怎么长的来着?”
外卖到了我都没想明白。
就这种时候,你就会开始怀疑人生:正则确实好用,但人类是不是不太适合直接和那堆符号打交道。
结果第二天早上,在工位上刷 GitHub 的时候刷到一个库,名字叫 Humre,说白了就是“给人看的正则”。我试了半天,觉得这东西确实挺适合推荐给大家的。
说人话的正则是个啥玩意儿
先把心态放平一点,Humre不是要推翻 re 模块、发明新正则引擎,它干的事儿特别简单粗暴:
它只帮你把“人能看懂的拼法”变成普通的正则字符串,然后还是交给 re.compile() 去跑。
所以你可以这么理解:
以前你自己手写: r'\d{3}-\d{3}-\d{4}'用 Humre 的时候,是写: exactly(3, DIGIT) + "-" + exactly(3, DIGIT) + "-" + exactly(4, DIGIT)最后这个拼出来的还是那个 \d{3}-\d{3}-\d{4},只是你平时看的、改的,是第二种形式
装一下库也很正常:
pip install humre
Python 3.5 往上的版本都能用,作者就是写《Automate the Boring Stuff with Python》的那个 Al Sweigart。
先拿最经典的例子开刀:手机号
刚才那个美国手机号的例子,咱直接对比一眼会更直观一点。
普通写法,大概这样:
import re
phone_re = re.compile(r'\d{3}-\d{3}-\d{4}')
Humre 的写法就变成了这样:
from humre import *
phone_pattern = compile(
exactly(3, DIGIT),
"-",
exactly(3, DIGIT),
"-",
exactly(4, DIGIT),
)
m = phone_pattern.search("Call 415-555-1234 today!")
print(m.group())
# 415-555-1234
几个点你感受一下:
DIGIT这个常量=\d,你肉眼看过去就知道是“一个数字”exactly(3, DIGIT)一眼就知道是“3个数字”中间的 "-"就是普通字符串,不用再纠结到底要不要转义
而 compile() 这个函数,其实就是包了一层 re.compile(),你也可以把 phone_pattern 看成一个正常的 re.Pattern 对象来用。
Humre 的核心套路:函数 + 常量,全部都是“字符串”
为啥说它“更人性化”,其实就两件事:
把那些鬼画符变成有名字的常量,比如 DIGIT、WORD、WHITESPACE等等把量词和分组变成普通函数,比如 one_or_more()、optional()、group()之类
重点有个小认知:Humre 的函数返回的都是普通字符串,常量本身也是字符串。
所以你可以像玩字符串一样把它到处拼:
from humre import *
# 5 位数字 + 可选空格 + 至少一个非空白
pattern_str = (
exactly(5, DIGIT)
+ optional(WHITESPACE)
+ one_or_more(NONWHITESPACE)
)
print(pattern_str) # \d{5}\s?\S+
# 甚至塞到 f-string 里
count = 2
pattern_str2 = f"I need {exactly(count, DIGIT)} apples"
print(pattern_str2) # I need \d{2} apples
你看,这样 IDE 的高亮、括号匹配、自动补全全都正常工作,根本不用去纠结那一堆反斜杠。
再来一个稍微贴近一点业务的例子:解析日志行
假设你线上日志长这样:
[2025-06-01 10:23:01] INFO user=alice ip=10.0.0.1 cost=123ms
我们一般想干几件事:
把时间戳抠出来 把日志等级抠出来 把 user、ip、cost 抠出来
先看传统 re 写法(不算太夸张的那种):
import re
log_re = re.compile(
r'^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\]\s+'
r'(INFO|WARN|ERROR)\s+'
r'user=([\w.-]+)\s+'
r'ip=([\d.]+)\s+'
r'cost=(\d+)ms$'
)
你过两周再回来,看见这么多 \d 和 \s+,脑子一定要重新编译一遍。
用 Humre 写一遍,会变成下面这样(故意用 named_group,更接近实战):
from humre import *
timestamp = group(
exactly(4, DIGIT), "-", exactly(2, DIGIT), "-", exactly(2, DIGIT),
WHITESPACE,
exactly(2, DIGIT), ":", exactly(2, DIGIT), ":", exactly(2, DIGIT),
)
level = group_either("INFO", "WARN", "ERROR")
user = named_group(
"user",
one_or_more(chars("A-Za-z0-9._-")),
)
ip = named_group(
"ip",
one_or_more(chars("0-9.")),
)
cost = named_group(
"cost",
one_or_more(DIGIT),
)
log_pattern = compile(
starts_with("["),
timestamp,
"]", one_or_more(WHITESPACE),
level, one_or_more(WHITESPACE),
"user=", user, one_or_more(WHITESPACE),
"ip=", ip, one_or_more(WHITESPACE),
"cost=", cost, "ms",
ends_with(""),
)
text = "[2025-06-01 10:23:01] INFO user=alice ip=10.0.0.1 cost=123ms"
m = log_pattern.search(text)
print(m.group("user"), m.group("ip"), m.group("cost"))
# alice 10.0.0.1 123
说实话代码行数是多了,但:
各部分是“拼出来”的,你可以一块块看 把 timestamp、user这些变量抽出来,哪天需要改格式,定位也更轻松named_group("user", ...)这种比(?P<user>...)还是友好多了([GitHub][1])
常用那几个 API,用几次就记住了
Humre 的 API 列表挺长的,真要全背下来也没必要。对日常开发来说,你记住几个“高频组合”就够用了:
量词系列
exactly(n, X):就是X{n}between(min, max, X):X{min,max}at_least(n, X):X{n,}one_or_more(X):X+zero_or_more(X):X*optional(X):X?分组 & 或者
group(X, Y, Z):相当于(XYZ)noncap_group(...):(?:...)不捕获either("A", "B", "C"):A|B|C边界
starts_with(X):^Xends_with(X):X$starts_and_ends_with(X):^X$字符类
chars("A-Z0-9"):[A-Z0-9]nonchars("A-Z"):[^A-Z]几个常量
DIGIT=\d,WORD=\w,WHITESPACE=\sANYTHING/EVERYTHING/SOMETHING一系列“匹配任意字符”的模式NUMBER、EURO_NUMBER这种已经写好的“带逗号的小数”模式
你没必要看文档背下来,实际用的时候 IDE 自动补全+文档提示就够了。上面这些就是 README 里的那一长串 quick reference 里,我个人感觉最常用的那一撮。([GitHub][1])
和 re 的“混搭”姿势
有同学可能会问一个很现实的问题:我不可能把老代码全换成 Humre 吧?
没必要。
因为 Humre 函数最终都会返回普通字符串,所以你完全可以混写,比如:
from humre import *
import re
# 大块复杂的部分用 Humre
user_part = one_or_more(WORD)
# 特简单的部分继续用原始写法
pattern = re.compile(
r"^user=" + user_part + r"\s+age=" + r"\d{1,3}$"
)
print(pattern.pattern)
# ^user=\w+\s+age=\d{1,3}$
甚至你可以只在“看不懂”的那部分上用 Humre,把复杂块单独抽成一个变量,让这坨东西看起来不那么吓人就行。
flags 也能用,而且还是那一套
有的时候,我们会给 re.compile 传些 flag,比如忽略大小写、多行匹配之类的。Humre 也是支持的,只是把常量拿到了自己名下,比如:
from humre import *
pattern = compile(
starts_with("error:"),
ANYTHING,
flags=IGNORECASE | MULTILINE,
)
text = """ERROR: first
warn: ...
error: second
"""
print(pattern.findall(text))
# ['ERROR: first', 'error: second']
IGNORECASE、MULTILINE 这些都和 re.IGNORECASE、re.MULTILINE 一一对应,底层还是传给 re.compile 去用。
性能会不会很拉胯?
这个问题我也好奇过,特地看了下作者的解释:Humre 的函数只是做一些字符串拼接,你只在“定义正则”的那一刻调用一次,后面用 pattern.search()、pattern.findall() 的时候,性能完全就是 Python 原生 re 的那一套,不会慢到哪去。
说人话就是:如果你项目慢到要考虑“拼正则字符串这几十个函数调用”的开销,那你大概率已经有更大的性能问题要先解决了。
随便举个小表单校验的例子哈,比如你有个注册页:
用户名:字母开头,后面可以跟数字、下划线、点,长度 4~16 邮箱:简单校验,不需要 RFC 那种地狱级别 年龄:0~120 的整数,先简单用正则做一层挡板
用 Humre 大概会写成这样:
from humre import *
import re
# 用户名:字母开头,后面字母/数字/下划线/点,4~16
username_pattern = compile(
starts_and_ends_with(
group(
LETTER, # 首字符字母
between(3, 15, chars("A-Za-z0-9._")), # 剩下 3~15
)
)
)
# 非严格邮箱,只大致挡一下
email_pattern = compile(
starts_and_ends_with(
group(
one_or_more(chars("A-Za-z0-9._%+-")),
"@",
one_or_more(chars("A-Za-z0-9.-")),
".",
between(2, 6, LETTER),
)
),
flags=IGNORECASE,
)
# 年龄:0~120,先用正则挡非数字 + 长度,再用 int 判断范围
age_pattern = compile(
starts_and_ends_with(
between(1, 3, DIGIT)
)
)
defis_valid_username(s: str) -> bool:
return bool(username_pattern.fullmatch(s))
defis_valid_email(s: str) -> bool:
return bool(email_pattern.fullmatch(s))
defis_valid_age(s: str) -> bool:
ifnot age_pattern.fullmatch(s):
returnFalse
return0 <= int(s) <= 120
if __name__ == "__main__":
print(is_valid_username("dong_01")) # True
print(is_valid_username("1dong")) # False
print(is_valid_email("[email protected]")) # True
print(is_valid_email("bad@@example")) # False
print(is_valid_age("25")) # True
print(is_valid_age("999")) # False
你想象一下,如果这一套用原始正则写完,是不是整个模块全是 r'^[A-Za-z][A-Za-z0-9._]{3,15}$' 这种东西?你每次改规则,都要从左到右数花括号和中括号,稍微走神一下就会漏掉一个字符。
我自己的习惯是:
简单的小匹配(比如 ^\d{4}$这类),继续用re原始写法一旦正则超过一行,或者开始出现一堆括号、管道,就考虑用 Humre 把结构写清楚
你要是最近刚好有一坨看不懂的老正则,可以抽一段用 Humre 改写试试,体会会比较明显。
行了,我得去把昨天遗留的那个日志过滤脚本改完了,不然明天凌晨报警又得被叫起来…
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html
虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB