📓
Study
  • README
  • Application
    • Contest
      • 竞赛trick
  • Basic Know
    • 半监督学习
    • 贝叶斯
      • 朴素贝叶斯分类器
    • 对抗训练
    • 概率图模型
      • CRF
      • HMM
      • 概率图模型
    • 关联分析
    • 归纳偏置
      • [什么是 Inductive bias(归纳偏置)?](BasicKnow/归纳偏置/什么是 Inductive bias(归纳偏置)?.md)
    • 聚类
    • 决策树
    • 绿色深度学习
    • 树模型&集成学习
      • 提升树
      • Ada Boost
      • [集成学习]
    • 特征工程
      • 数据分桶
      • 特征工程概述
      • 特征选择
      • LDA
      • PCA
    • 线性模型
      • 感知机
      • 最大熵模型
      • SVM
        • SVM支持向量机
      • 逻辑回归
      • 线性回归
    • 优化算法
      • 拉格朗日对偶性
      • 牛顿法
        • 牛顿法&拟牛顿法
      • 梯度下降法
        • 梯度下降算法
      • 优化算法
    • 预处理
      • [1-1]正则表达式
      • [1-2]文本预处理
      • [1-3]词性
      • [1-4]语法分析
      • [1-6]文本分类
      • [1-7]网络爬取
      • 【备用】正则表达式
      • 7.re模块
      • 词典匹配
      • 分词
      • 子表达式
      • Todo
    • 主题模型
      • LDA
    • Deep Learning
      • 反向传播
      • 梯度消失&梯度爆炸
      • Batch Size
      • 1.DLbasis
      • 小概念
      • MLstrategy
      • CNN
      • RNN及其应用
      • 关于深度学习实践
      • 神经网络概述
      • Batch Normalization
      • Program CNN
      • Program D Lbasis
      • Program DN Nimprove
      • Program Neural Style Transfer
      • Summer DL
    • EM算法
    • GAN
      • Gans In Action Master
    • GNN
      • 搜广推之GNN
      • Representation Learning
        • Anomalydetection
        • Conclusion
        • Others
        • Papernotes
        • Recommadation
    • k近邻法
      • K近邻
    • Language Model
      • 语言模型解码采样策略
      • [1-1][语言模型]从N-gram模型讲起
      • [1-2][语言模型]NNLM(神经网络语言模型)
      • [1-3][语言模型]基于RNN的语言模型
      • [1-4][语言模型]用N-gram来做完形填空
      • [1-5][语言模型]用KenLM来做完形填空
    • Loss Function
      • 常用损失函数
      • Focal Loss
      • softmax+交叉熵
    • Machine Learning
      • [基础]概念
      • 待整合
      • 交叉验证
      • 无监督学习
      • 优缺点
      • ML Yearning
      • SVD
    • Statistics Math
      • 程序员的数学基础课
      • 数学基础
      • 统计&高数
      • 统计题目
      • 线性代数
      • 组合数学
      • Discrete Choice Model
      • Nested Choice Model
  • Course Note
    • 基于TensorFlow的机器学习速成课程
      • [Key ML Terminology](CourseNote/基于TensorFlow的机器学习速成课程/Key ML Terminology.md)
    • 集训营
      • 任务说明
      • 算法实践1.1模型构建
      • 算法实践1.2模型构建之集成模型
      • 算法实践2.1数据预处理
    • 李宏毅机器学习
      • 10DNN训练Tips
        • Chapter 18
      • 16无监督学习
        • Chapter 25
    • 贪心NLP
      • 贪心NLP笔记
    • Cs 224 N 2019
      • [A Simple But Tough To Beat Baseline For Sentence Embeddings](CourseNote/cs224n2019/A Simple but Tough-to-beat Baseline for Sentence Embeddings.md)
      • [Lecture 01 Introduction And Word Vectors](CourseNote/cs224n2019/Lecture 01 Introduction and Word Vectors.md)
      • [Lecture 02 Word Vectors 2 And Word Senses](CourseNote/cs224n2019/Lecture 02 Word Vectors 2 and Word Senses.md)
      • [Lecture 03 Word Window Classification Neural Networks And Matrix Calculus](CourseNote/cs224n2019/Lecture 03 Word Window Classification, Neural Networks, and Matrix Calculus.md)
      • [Lecture 04 Backpropagation And Computation Graphs](CourseNote/cs224n2019/Lecture 04 Backpropagation and Computation Graphs.md)
      • [Lecture 05 Linguistic Structure Dependency Parsing](CourseNote/cs224n2019/Lecture 05 Linguistic Structure Dependency Parsing.md)
      • [Lecture 06 The Probability Of A Sentence Recurrent Neural Networks And Language Models](CourseNote/cs224n2019/Lecture 06 The probability of a sentence Recurrent Neural Networks and Language Models.md)
      • Stanford NLP
    • Deep Learning Book Goodfellow
      • Books
        • Deep Learning Book Chapter Summaries Master
      • 提纲
      • C 5
      • C 6
      • [Part I Applied Math And Machine Learning Basics](CourseNote/Deep-Learning-Book-Goodfellow/Part I - Applied Math and Machine Learning basics.md)
    • Lihang
    • NLP实战高手课
      • 极客时间_NLP实战高手课
    • 工具&资料
    • 机器学习、深度学习面试知识点汇总
    • 七月kaggle课程
    • 算法工程师
    • 贪心科技机器学习必修知识点特训营
    • 唐宇迪机器学习
    • 语言及工具
    • AI技术内参
    • Suggestions
  • Data Related
    • 数据质量
      • 置信学习
    • 自然语言处理中的数据增广_车万翔
      • 自然语言处理中的数据增广
    • Mixup
    • 数据不均衡问题
    • 数据增强的方法
  • Knowledge Graph
    • Information Extraction
      • 联合抽取
        • PRGC
      • Code
        • BERT微调
      • NER
        • 阅读理解做NER
          • MRC
        • FLAT
        • Global Pointer
        • 命名实体识别NER
    • Keyword Extraction
      • 关键词抽取
    • 小米在知识表示学习的探索与实践
    • KG
  • Multi Task
    • EXT 5
      • Ex T 5
  • NLG
    • Dailogue
      • 比赛
        • 对话评估比赛
          • [simpread-DSTC10 开放领域对话评估比赛冠军方法总结](NLG/Dailogue/比赛/对话评估比赛/simpread-DSTC10 开放领域对话评估比赛冠军方法总结.md)
      • 任务型对话
        • DST
          • DST概述
        • NLG
          • NLG概述
        • NLU
          • NLU概述
        • 任务型对话概述
        • simpread-任务型对话系统预训练最新研究进展
      • 问答型对话
        • 检索式问答
          • 基于预训练模型的检索式对话系统
          • 检索式文本问答
        • 业界分享
          • 低资源场景下的知识图谱表示学习和问答_阿里_李杨
          • QQ浏览器搜索智能问答
        • 问答型对话系统概述
      • 闲聊型对话
        • 闲聊型对话系统概述
      • 业界分享
        • 人工智能与心理咨询
        • 腾讯多轮对话机器人
        • 微软小冰
        • 小布助手闲聊生成式算法
        • 美团智能客服实践_江会星
        • 去哪儿智能客服探索和实践
        • 实时语音对话场景下的算法实践_阿里_陈克寒
        • 智能语音交互中的无效query识别_小米_崔世起
        • UNIT智能对话
      • 主动对话
      • EVA
        • EVA分享
        • EVA模型
      • PLATO
      • RASA
    • Machine Translation
      • 业界分享
        • 爱奇艺台词翻译分享
      • Paper
        • Deep Encoder Shallow Decoder
    • RAGRelated
    • Text 2 SQL
      • M SQL
        • [M SQL 2](NLG/Text2SQL/M-SQL/M-SQL (2).md)
      • [Text2SQL Baseline解析](NLG/Text2SQL/Text2SQL Baseline解析.md)
      • Text 2 SQL
    • Text Summarization
      • [文本摘要][paper]CTRLSUM
      • 文本摘要
  • Pre Training
    • 业界分享
      • 超大语言模型与语言理解_黄民烈
        • 超大语言模型与语言理解
      • 大模型的加速算法_腾讯微信
        • 大模型的加速算法
      • 孟子轻量化预训练模型
      • 悟道文汇文图生成模型
      • 悟道文澜图文多模态大模型
      • 语义驱动可视化内容创造_微软
        • 语义驱动可视化内容创造
    • Base
      • Attention
      • Mask
        • NLP中的Mask
      • Position Encoding
        • 位置编码
    • BERT
      • ALBERT
      • Bert
        • Venv
          • Lib
            • Site Packages
              • idna-3.2.dist-info
                • LICENSE
              • Markdown-3.3.4.dist-info
                • LICENSE
              • Tensorflow
                • Include
                  • External
                    • Libjpeg Turbo
                      • LICENSE
                  • Unsupported
                    • Eigen
                      • CXX 11
                        • Src
                          • Tensor
              • Werkzeug
                • Debug
                  • Shared
                    • ICON LICENSE
        • CONTRIBUTING
        • Multilingual
      • Ro BER Ta
      • BERT
      • BERT面试问答
      • BERT源码解析
      • NSP BERT
    • BERT Flow
    • BERT Zip
      • Distilling The Knowledge In A Neural Network
      • TINYBERT
      • 模型压缩
    • CPM
    • CPT
      • 兼顾理解和生成的中文预训练模型CPT
    • ELECTRA
    • EL Mo
    • ERNIE系列语言模型
    • GPT
    • MBART
    • NEZHA
    • NLG Sum
      • [simpread-预训练时代下的文本生成|模型 & 技巧](Pre-training/NLGSum/simpread-预训练时代下的文本生成|模型 & 技巧.md)
    • Prompt
      • 预训练模型的提示学习方法_刘知远
        • 预训练模型的提示学习方法
    • T 5
      • Unified SKG
      • T 5
    • Transformer
    • Uni LM
    • XL Net
    • 预训练语言模型
    • BERT变种
  • Recsys
    • 多任务Multi-task&推荐
    • 推荐介绍
    • 推荐系统之召回与精排
      • 代码
        • Python
          • Recall
            • Deep Match Master
              • Docs
                • Source
                  • Examples
                  • FAQ
                  • Features
                  • History
                  • Model Methods
                  • Quick Start
    • 业界分享
      • 腾讯基于知识图谱长视频推荐
    • 召回
    • Sparrow Rec Sys
    • 深度学习推荐系统实战
    • 推荐模型
    • Deep FM
  • Search
    • 搜索
    • 业界分享
      • 爱奇艺搜索排序算法实践
      • 语义搜索技术和应用
    • 查询关键字理解
    • 搜索排序
    • BM 25
    • KDD21-淘宝搜索中语义向量检索技术
    • query理解
    • TFIDF
  • Self Supervised Learning
    • Contrastive Learning
      • 业界分享
        • 对比学习在微博内容表示的应用_张俊林
      • Paper
      • R Drop
      • Sim CSE
    • 自监督学习
  • Text Classification
    • [多标签分类(Multi-label Classification)](TextClassification/多标签分类(Multi-label Classification)/多标签分类(Multi-label Classification).md)
    • Fast Text
    • Text CNN
    • 文本分类
  • Text Matching
    • 文本匹配和多轮检索
    • CNN SIM
    • Word Embedding
      • Skip Gram
      • Glove
      • Word 2 Vec
    • 文本匹配概述
  • Tool
    • 埋点
    • 向量检索(Faiss等)
    • Bigdata
      • 大数据基础task1_创建虚拟机+熟悉linux
      • 任务链接
      • Mr
      • Task1参考答案
      • Task2参考答案
      • Task3参考答案
      • Task4参考答案
      • Task5参考答案
    • Docker
    • Elasticsearch
    • Keras
    • Numpy
    • Python
      • 可视化
        • Interactivegraphics
        • Matplotlib
        • Tkinter
        • Turtle
      • 数据类型
        • Datatype
      • python爬虫
        • Python Scraping Master
          • phantomjs-2.1.1-windows
        • Regularexp
        • Scrapying
        • Selenium
      • 代码优化
      • 一行代码
      • 用python进行语言检测
      • Debug
      • Exception
      • [Features Tricks](Tool/python/Features & Tricks.md)
      • Fileprocess
      • Format
      • Functional Programming
      • I Python
      • Magic
      • Math
      • Os
      • Others
      • Pandas
      • Python Datastructure
      • Python操作数据库
      • Streamlit
      • Time
    • Pytorch
      • Dive Into DL Py Torch
        • 02 Softmax And Classification
        • 03 Mlp
        • 04 Underfit Overfit
        • 05 Gradient Vanishing Exploding
        • 06 Text Preprocess
        • 07 Language Model
        • 08 Rnn Basics
        • 09 Machine Translation
        • 10 Attention Seq 2 Seq
        • 11 Transformer
        • 12 Cnn
        • 14 Batchnorm Resnet
        • 15 Convexoptim
        • 16 Gradientdescent
        • 17 Optim Advance
    • Spark
      • Pyspark
        • pyspark之填充缺失的时间数据
      • Spark
    • SQL
      • 数据库
      • Hive Sql
      • MySQL实战45讲
    • Tensor Flow
      • TensorFlow入门
  • Common
  • NLP知识体系
Powered by GitBook
On this page
  • 基本的正则表达式
  • 匹配单个字符
  • 匹配一组字符
  • 使用元字符
  • 重复匹配
  • 位置匹配
  • 子表达式(分组)
  • 回溯引用:前后一致匹配(反向引用)
  • 前后查找/环视(非捕获分组)
  • 嵌入条件
  • 匹配模式
  • 转义
  • Unicode
  • 匹配原理
  • 正则表达式的常见操作
  • 正则表达式的优化建议
  • 常用的操作符模式总结
  • 参考资料

Was this helpful?

  1. Basic Know
  2. 预处理

[1-1]正则表达式

正则表达式(regular expression, 简称RE)描述了一种字符串匹配的模式(pattern),可以用来检查一个字符串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。

通过使用正则表达式,可以:

  • 测试字符串内的模式。 例如,可以测试输入字符串,以查看字符串内是否出现电话号码模式或信用卡号码模式。这称为数据验证。

  • 替换文本。 可以使用正则表达式来识别文档中的特定文本,完全删除该文本或者用其他文本替换它。

  • 基于模式匹配从字符串中提取子字符串。 可以查找文档内或输入域内特定的文本。例如Linux中的grep命令使用正则表达式查找文件里符合表达式条件的内容。

基本的正则表达式

正则表达式语法由字符和操作符构成, 字符例如字母和数字表示他们自身。很多标点符号只有被转义时才匹配自身,否则它们表示特殊的含义。

最简单的正则表达式是一串字符,例如woodchuck表达式可以匹配字符串"woodchuck",!可以匹配感叹号"!"。正则表达式是大小写敏感的,所以woodchuck无法匹配到"Woodchuck",可以使用[wW]oodchuck匹配"Woodchuck"和"woodchuck"。其中[...]用来表示单个字符的取值范围。

下面结合例子来熟悉一下正则表达式,用下划线来表示正则匹配到的字符串。

正则表达式
匹配
举例
涉及知识点

[wW]oodchuck

Woodchuck或woodchuck

Woodchuck

大小写敏感,[]表示字符取值范围

[abc]

'a', 'b'或'c'

In uomini, in soldati

[]表示字符取值范围

[0-5]

0-5之间的数字

plenty of 7 of 5

-简单指定范围

[A-Z]

一个大写字母

Mr.Li

-简单指定范围

[0-9]

单个数字,等价于[1234567890]

Chapter 1

-简单指定范围

[^A-Z]

非大写字母

Mr

[^...]表示非指定字符之外的字符。

[e^]

匹配'e'或者'^'

look up ^

^不在[]中第一个字符时不表示“非”

a^b

匹配"a^b"

look a^b

同上

^the

匹配字符串的the

the dog or the cat

^在模式第一个字符时表示字符串的开头

the dog\.$

匹配以the dog.结尾的字符串

the cat or the dog.

$表示字符串的结尾

woodchucks?

匹配woodchuck或woodchucks

woodchuck

?表示前一个字符出现0次或1次

colou?r

color或colour

colour

同上

aa*

a或aa或aaa...

aaaa

*表示前一个字符出现0次或多次

[ab]*

0个或多个连续的a或b

aaa, abab, bbb

同上

[0-9][0-9]*或[0-9]+

1个或多个连续的数字

$123

+表示前一个字符出现1次或多次

beg.n

beg+任意字符+n

begin, beg'n, begun

.表示任意单个字符(除了换行符)

P14

匹配单个字符

1.匹配纯文本,比如Ben,think等。

  • 可能有多个匹配结果,比如Python中的re.find是只返回第一个匹配结果,re.findall是返回所有匹配结果。

  • 字母是大小写敏感的,B只能匹配B,不能匹配b。Python库中的参数re.I可以设置是否忽略大小写。

2.匹配任意单个字符用.(实际上它不能匹配换行符)

3.匹配特殊字符。一些字符在正则表达式里有特殊的含义,比如.表示任意单个字符,如果想匹配.这个字符,就要用转移字符\,即\.就可以匹配.这个字符。

匹配一组字符

1.匹配多个字符中的某一个用[],比如[abc]表示匹配a或b或c

2.利用字符集合区间,比如[0-9]表示匹配0-9里的任意数字, 其它的字符集合区间还有[A-Z], [a-z], [a-f]等

3.取非匹配^,也就是说,除了那个字符集合里的字符,其它字符都可以匹配。比如[^0-9]匹配任意不是数字的字符。

re.search是Python的正则表达式操作函数,表示“进行正则表达式匹配”,charStr是需要判断的字符串,"[0123456789]"则是以字符串形式给出的正则表达式。

# 能匹配则返回RegexObject,否则返回None
re.search("[0-9]", charStr)

在很多语言中还可以用转义序列\xhex来表示一个字符,其中\x是固定前缀,表示转义序列的开头,num是字符对应的码值(Code Point),是一个两位的十六进制数值。比如字符A的码值是41(十进制则为65),所以也可以用\x41表示。

# 字符组
re.search("^[\x00-\x7F]$", charStr)

使用元字符

1.元字符是一些在正则表达式里有有特殊含义的字符,这些字符无法用来代表它本身。只有在元字符前面加上反斜杠\才能用来表示它本身。

比如要匹配\本身,也要在前面加上\,得到\\。

2.匹配空白字符

元字符
说明

[\b]

回退(并删除)一个字符(Backspace键)

\f

换页符

换行符

回车符

制表符(Tab键)

\v

垂直制表符

比如\r匹配一个“回车+换行”组合,有许多操作系统(比如Windows)都把这个组合用作文本行的结束标签。Unix和Linux系统只使用一个换行符来结束一个文本行。

3.匹配特定的字符类别

元字符
说明

\d

任何一个数字字符(等价于[0-9])

\D

任何一个非数字字符(等价于[^0-9])

\w

任何一个字符数字字符(大小写均可)或下划线字符(等价于[a-zA-Z0-9_])

\W

任何一个非字母数字或下划线字符(等价于[^a-zA-Z0-9_])

\s

任何一个空白字符(等价于[\f\n\r\t\v])

\S

任何一个非空白字符(等价于[^\f\n\r\t\v])

十六进制,比如\x0A对应于ASCII字符10(换行符),其效果等价于

八进制,比如\011对应于ASCII字符9(制表符),其效果等价于。

\s\S, \w\W, \d\D可以匹配任意字符,大小写是互补的。

4.使用POSIX字符类

POSIX字符类是许多(但不是所有)正则表达式实现都支持的一种简写形式。

字符类
说明

[:alnum:]

任何一个字母或数字(等价于[a-zA-Z0-9])

[:alpha:]

任何一个字母(等价于[a-zA-Z])

[:blank:]

空格或制表符(等价于[\t ])

[:cntrl:]

ASCII控制字符(ASCII 0到31,再加上ASCII 127)

[:digit:]

任何一个数字(等价于[0-9])

[:graph:]

和[:print:]一样,但不包括空格

[:lower:]

任何一个小写字母(等价于[a-z])

[:print:]

任何一个可打印字符

[:punct:]

既不属于[:alnum:]也不属于[:cntrl:]的任何一个字符

[:space:]

任何一个空白字符,包括空格(等价于[^\f\n\r\t\v ])

[:upper:]

任何一个大写字母(等价于[A-Z])

[:xdigit:]

任何一个十六进制数字(等价于[a-fA-F0-9])

注意[]是POSIX字符类本身的组成部分

重复匹配

匹配一个或多个字符:给字符(或字符集合)加上+字符作为后缀

匹配零个或多个字符:给字符(或字符集合)加上*字符作为后缀

匹配零个或一个字符:给字符(或字符集合)加上?字符作为后缀

为重复匹配次数设定一个精确的值,{数字}

为重复匹配次数设定一个区间,{最小值,最大值},注意是闭区间

匹配“至少重复多少次”,{最小值,}

防止过度匹配:*和+都是“贪婪型”元字符,它们在进行匹配时会尽可能地从一段文本的开头一直匹配到这段文本的末尾,而不是从这段文本的开头匹配到碰到第一个匹配为止。

这些元字符的“懒惰型”版本是给贪婪型元字符加上一个?后缀。

常用的贪婪型元字符有*, +, {n,},他们的懒惰型版本分别是*?, +?, {n,}?

位置匹配

\b用来匹配一个单词的开始或结尾(单词边界),它匹配的是一个这样的位置,这个位置位于一个能够用来构成单词的字符(字母、数字和下划线,也就是与\w相匹配的字符)和一个不能用来构成单词的字符(也就是与\W相匹配的字符)之间。它可以匹配句子的第一个单词。

\B匹配非单词边界,即匹配字母数字下划线之间,或者非字母数字下划线之间(取决于想匹配的字符)。

比如\B-\B去匹配Please enter the nine-digit id as it appears on your color - coded pass-key这句话,nine-digit和pass-key中的连字符不能与之匹配,但color - coded中的连字符可以与之匹配。

用\Bi\B去匹配上述句子,nine-digit里的3个i都能匹配,剩下的不能匹配。

注意除了字母数字下划线,其它的字符都视为单词边界

字符串边界:匹配字符串开头用^,结尾用$

分行匹配模式(?m)将使得正则表达式引擎把行分隔符当作一个字符串分隔符来对待。这个模式必须出现在整个模式的最前面。比如(?m)^\w+可以提取字符串中每行的第一个单词。(?m)\w+$可以提取字符串中每行的最后一个单词。

不同平台的行终止符:

  • Unix/Linux:

  • Windows:\r

  • Mac OS:

如果我们不想定位到字符串内部的行起始位置,只关心整个字符串的起始位置,则可以使用\A,绝大多数工具的正则表达式都支持。(?m)\A\w+匹配整段文本的第一个单词。

\Z和\z不受多行模式的影响,在任何情况下都匹配整个字符串的结束位置。\Z等价于默认模式(非多行模式)下的$,如果字符串的末尾有行终止符,则它匹配换行符之前的位置;\z则不管行终止符,只匹配整个字符串的结束位置。

注意Python不支持\z,Python的\Z等价于其他语言中的\z.

# 数据验证
# $匹配结尾行终止符之前的位置,会忽略末尾的行终止符
re.search(r"^\d{6}$", "123456\n") != None   # True
# ^和$一般成对出现,这里用\A更符合习惯
re.search(r"\A\d{6}\Z", "123456\n") != None   # False

^和$进行正则表达式替换时并不会被替换。常见的应用是将纯文本转换为HTML,比如将纯文本的电子文档转换成ePub格式。最简单的思路是使用多行模式将^替换为<p>,将$替换为</p>。

^和$的替换

plainText = "line1\nline2\nline3"
re.sub("(?m)$", "</p>", re.sub("(?m)^", "<p>", plainText))
# <p>line1</p>\n<p>line2</p>\n<p>line3</p>

去除行首的空白字符

withSpaces = "   begin\n between\t\n\nend  "
beginSpaceRegex = "(?m)^\s+"
trimmedLeadingSpace = re.sub(beginSpaceRegex, "", withSpaces)
trimmedLeadingSpace
# begin\nbetween\t\nend  (末尾的空白字符没有删掉)
endSpaceRegex = "(?m)\s+$"
trimmedEndingSpace = re.sub(endSpaceRegex, "", trimmedLeadingSpace)
trimmedEndingSpace
# begin\nbetween\nend(末尾的空白字符已经删掉)

# 能不能一步到位呢?
withSpaces = "   begin\n between\t\n\nend  "
spaceRegex = r"(?m)(^\s+|\s+$)"
spaceTrimmed = re.sub(spaceRegex, "", withSpaces)
spaceTrimmed
# begin\nbetweenend(第二行和第四行合并了)

子表达式(分组)

子表达式用()括起来,子表达式是一个独立元素。比如(19|20)\d{2}匹配19或20开头的4位数字(这种形式称为多选结构),多选结构可以没有括号,比如19|20,但还是推荐写括号。

多选结构的排列是有讲究的,比如(jeff|jeffrey)用来匹配jeffrey,结果会是什么呢?这个问题没有标准答案,Java, .NET, Python, Ruby, JavaScript, PHP中都会优先选择最左侧的分支。在平时使用时应当尽量避免多选分支中存在重复匹配,因为这样会大大增加回溯的计算量。也就是说,应当避免这样的情况:针对多选结构(option1|option2),某段文本既可以由option1匹配,也可以由option2匹配。

例子:匹配合法的IP地址,地址的各位数字必须符合的规则是

  • 任何一个1位或2位数字

  • 任何一个以1开头的3位数字

  • 任何一个以2开头、第2位数字在0~4之间的3位数字

  • 任何一个以25开头、第3位数字在0~5之间的3位数字

正则表达式:(((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))\.){3}((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))

  • 匹配月:(0?[1-9]|1[012])

  • 匹配日:(0?[1-9]|[12]\d|3[01])

  • 匹配小时:(0?[1-9]|[01]\d|2[0-4])

  • 匹配分钟:(0?[1-9]|[0-5]\d|60)

  • 匹配手机号码:(0|\+86)?(13[0-9]|15[0-356]|18[025-9])\d{8}

引用分组(捕获分组)

re.search("(\d{4})-(\d{2})-(\d{2})", "2010-12-22").group(1)
# 2010
re.search("(\d{4})-(\d{2})-(\d{2})", "2010-12-22").group(2)
# 12
re.search("(\d{4})-(\d{2})-(\d{2})", "2010-12-22").group(3)
# 22
re.search("(\d{4})-(\d{2})-(\d{2})", "2010-12-22").group(0)
# 2010-12-22
re.search("(\d{4})-(\d{2})-(\d{2})", "2010-12-22").group()
# 2010-12-22

无论括号如何嵌套,分组的编号都是根据开括号出现顺序来计数的。

容易犯错的:

re.search("(\d{4})-(\d{2})-(\d{2})", "2010-12-22").group(1)
# 2010
re.search("(\d){4}-(\d{2})-(\d{2})", "2010-12-22").group(1)
# 0
# 编号为1的分组匹配的文本的值依次是2、0、1、0,最后的结果是0

在替换中使用分组

re.sub("(\d{4})-(\d{2})-(\d{2})", r"\2/\3/\1", "2010-12-22")
# 12/22/2010
re.sub("(\d{4})-(\d{2})-(\d{2})", r"\1年\2年\3日", "2010-12-22")
# 2010年12月22日

因为\1,\2不是字符串中的合法转义序列,所以必须指定为原生字符串,在字符串前面加一个"r"。

如果想引用整个表达式匹配的文本,不能使用\0,因为\0开头的转义序列通常表示用八进制形式表示的字符,\0本身表示ASCII字符编码为0的字符。如果一定要引用整个表达式匹配的文本,则可以稍加变通,给整个表达式加上一对括号,之后用\1来引用。

re.sub("((\d{4})-(\d{2})-(\d{2}))", "[\\1]", "2010-12-22")
# [2010-12-22]
re.sub("((\d{4})-(\d{2})-(\d{2}))", r"[\1]", "2010-12-22")
# [2010-12-22]

回溯引用:前后一致匹配(反向引用)

回溯引用允许正则表达式模式引用前面的匹配结果

比如在一段文本里找出所有连续2次重复出现的单词,[ ]+(\w+)[ ]+\1,\1就是一个回溯引用,引用的是前面划分出来的子表达式(w+)

\1代表模式里的第1个子表达式,\2代表第2个子表达式...

匹配HTML的任何一级标题的开始标签和与之配对的结束标签(忽略任何不配对的标签组合),<[hH]([1-6])>.*?</[hH]\1>

回溯引用在替换操作中的应用

  • 把电话号码313-555-1234重新排版为(313) 555-1234

  • 正则表达式(\d{3})(-)(\d{3})(-)(\d{4})

  • 替换($1) $3-$5

大小写转换(只有部分正则表达式的实现支持)

元字符
说明

\E

结束\L或\U转换

\l

把下一个字符转换为小写

\L

把\L到\E之间的字符全部转换为小写

\u

把下一个字符转换为大写

\U

把\U到\E之间的字符全部转换为大写

比如把一级标题的标题文字转换为大写

正则表达式:(<[hH]1>)(.*?)(</[hH]1>)

替换:$1\U$2\E$3

用反向引用匹配重复字母

re.search(r"^([a-z])\1$", "aa") != None   # True
re.search(r"^([a-z])\1$", "ac") != None   # False

解析HTML代码时匹配tag:<([^>]+)>[\s\S]*?</\1>

如果tag后面有属性,例如<span class="class1">text</span>。就要修改表达式为<([a-zA-Z0-9]+)(\s[^>]+)?>[\s\S]*?</\1>

可能具有二义性的反向引用:

\10是表示第10个捕获分组还是第1个捕获分组之后跟着一个字符0呢?

在Python中,\10会被解释成第10个捕获分组匹配的文本,下面的程序会报错:

print(re.sub(r"(\d)", r"\10", "123"))

如果希望效果是第1个捕获分组之后跟着一个字符0,那就要写成\g<1>0

re.sub(r"(\d)", r"\g<1>0", "123")
# 102030

命名分组

标识分组为容易记忆和辨别的名字,而不是数字编号。

Python中用(?P<name>regex)来分组。用法举例:

namedRegex = r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})"
result = re.search(namedRegex, "2010-12-22")
print(result.group("year"))  # 2010,等价于result.group(1)
print(result.group("month"))  # 12,等价于result.group(2)
print(result.group("day"))  # 22,等价于result.group(3)

即便使用了命名分组,每个命名分组同时也具有数字编号。

Python中,如果使用了命名分组,在表达式反向引用时,必须使用(?P=name)的记法,而要进行正则表达式替换,则要写作\g<name>

re.search(r"^(?P<char>[a-z])(?P=char)$", "aa") != None  #True
re.sub("(?P<digit>\d)", r"\g<digit>0", "123")  # 102030

非捕获分组

(?:...)只能限定量词的作用范围,不捕获任何文本。在引用分组时,分组的编号同样会按开括号出现的顺序从左到右递增,只是必须以捕获分组为准,会略过非捕获分组。

re.search(r"(\d{4})-(\d{2})-(\d{2})", "2010-12-22").group(2)
# 12
re.search(r"(?:\d{4})-(\d{2})-(\d{2})", "2010-12-22").group(1)
# 12

有些结构并不真正匹配文本,而只负责判断在某个位置左右侧的文本是否符合要求,这种结构被称为断言(assertion)。常见的断言有三类:单词边界、行起始/结束位置、环视。

断言不匹配任何字符,只匹配位置;而反向引用只引用之前的捕获分组匹配的文本,之前捕获分组中锚点表示的位置信息,在反向引用时并不会保留下来。

如果表达式是(\bcat\b)\s+\1,\1所匹配的,就不只有单独出现的cat,还包括单词内部的cat(比如cate中的cat),如果要验证单词cat是否在字符串中出现了两次,正确的做法是在反向引用两端加上单词边界,变成(\bcat\b).*?\b\1\b

re.search(r"(\bcat\b).*?\1", "cat cate") != None  # True
re.search(r"(\bcat\b)\s+\b\1\b", "cat cate") != None  # False
re.search(r"(\bcat\b)\s+\b\1\b", "cat cat") != None  # True

前后查找/环视(非捕获分组)

环视类似单词边界,在它旁边的文本需要满足某种条件,而且本身不匹配任何字符。

向前查找指定了一个必须匹配但不在结果中返回的模式。向前查找实际就是一个以?=开头的子表达式,需要匹配的文本跟在后面。

模式.+(:)查找到并且匹配结果包含:,模式.+(?=:)查找到但匹配结果不包含:。

向前查找是查找出现在匹配文本之后的字符,但不消费那个字符。向后查找是查找出现在被匹配文本之前的字符,但不消费它。向后查找操作符是?<=

比如匹配价格。\$[0-9.]+匹配$开头的价格,会返回$。(?<=\$)[0-9.]+不会返回$。

向前查找模式的长度是可变的,它们可以包含.和+之类的元字符,所以非常灵活。

向后查找模式只能是固定长度。

向前查找和向后查找可以结合起来。

对前后查找取非,即查找不与给定模式相匹配的文本,称为负向前/后查找

各种前后查找操作符:

操作符
说明

(?=)

正向前查找(肯定顺序环视)

(?!)

负向前查找(否定顺序环视)

(?<=)

正向后查找(肯定逆序环视)

(?<!)

负向后查找(否定逆序环视)

否定的意思是如果正则表达式匹配成功,则在当前位置匹配失败。

顺序是指当前位置右侧,逆序是指当前位置的左侧。肯定是指子表达式能匹配的字符串必须出现,否定则是子表达式能匹配的字符串不能出现。

  • (?=\d{3})右侧必须出现三个数字字符

  • (?!\d{3})右侧不能出现三个数字字符

  • (?<=\d{3})左侧必须出现三个数字字符

  • (?<!\d{3})左侧必须出现三个数字字符

比如在一段有价格和数量的文本里,I paid $30 for 100 apples

匹配价格用(?<=\$)\d+,匹配数量用\b(?<!\$)\d+\b

准确匹配open tag:

openTagRegex = r"\A<(?!/)('[^']*'|\"[^\"]*\"|[^'\">])+(?<!/)>\Z"
re.search(openTagRegex, "<input name=txt value=\">\">") != None   # True
re.search(openTagRegex, "<input name=txt value='>'>") != None   # True
re.search(openTagRegex, "<u>") != None   # True
re.search(openTagRegex, "<br/>") != None   # False
re.search(openTagRegex, "<img src=\"url\"/>") != None   # False

格式化数字字符串,加入逗号的位置是:右侧的数字字符串的长度是3的倍数(且只能是3的倍数,不能有多余的数字),且左侧也是数字字符。

re.sub(r"(?<=\d)(?=(\d{3})+(?!\d))", r",","123456")
# 123,456

去掉中英文混排文本中不必要的空白字符

mixedString = " 中 英文混排,some English word,有多余的空 白字符  "
negativeSpaceTrimRegex = r"(?<![a-zA-Z])\s+(?![a-zA-Z])"
print("["+re.sub(negativeSpaceTrimRegex, "", mixedString)+"]")
# [中英文混排,some English word,有多余的空白字符]
positiveSpaceTrimRegex = r"(?<=[^a-zA-Z])\s+(?=[^a-zA-Z])"
print("["+re.sub(positiveSpaceTrimRegex, "", mixedString)+"]")
# [ 中英文混排,some English word,有多余的空白字符 ]

肯定环视要判断成功,字符串中必须有字符由环视结构中的表达式匹配;而否定环视要判断成功,却有两种情况:字符串中出现了字符,但这些字符不能由环视结构中的表达式匹配;或者字符串中不再有任何字符,也就是说,这个位置是字符串的起始位置或者结束位置。

在电子邮件地址中,进行主机名验证。主机名以点号分隔为多个域名字段,每个域名字段可以包含大小写字母、数字字母、横线,但是横线不能出现在开头位置。每个域名字段的长度最多为63个字符,整个主机名的长度最多为255个字符。

hostnameRegex = r"^(?=[-a-zA-Z0-9.]{0,255}(?![-a-zA-Z0-9.]))((?!-)[-a-zA-Z0-9]{1,63}\.)*((?!-)[-a-zA-Z0-9]){1,63}$"
re.search(hostnameRegex, "localhost") != None  # True
re.search(hostnameRegex, "example.com") != None  # True
re.search(hostnameRegex, "-example.com") != None  # False
re.search(hostnameRegex, ("e" * 64)+".com") != None  # False
re.search(hostnameRegex, "e"*256) != None  # False

一般来说,凡是从文本中提取“有长度特征的数据”,都需要用到环视。比如准确匹配6位数字构成的字符串,(?<!\d)\d{6}(?!\d)

从26个字母中减去5个辅音字母,(?![aeiou])[a-z]

环视结构中的括号只是结构需要,并不影响捕获分组。

环视结构中出现了捕获型括号,会影响分组

print(re.search(r"^(?=(ab|cd))", "abcd")).group(0)

print(re.search(r"^(?=(ab|cd))", "abcd")).group(1)
# ab
print(re.search(r"^(?=(?:ab|cd))", "abcd")).group(1)
# 报错no such group

环视结构中的捕获型括号一旦匹配完成,就不能回溯。

Python规定在逆序环视中的表达式能匹配的文本长度必须是固定的。(?<=dog)和(?<=(dog|cat))都是合法的,而(?<=dog?)和(?<=(dog|cats))都不合法,因为环视中的子表达式能匹配的文本长度不确定。

可以用多选结构来改写。((?<=dog)|(?<=dogs)), ((?<=dog)|(?<=cats))。

环视的组合

环视匹配的并不是字符,而是位置。多个环视可以组合在一起,实现同一个位置的多重判断。

环视中包含环视:(?=[-a-zA-Z0-9.]{0,255}(?![-a-zA-Z0-9.]))保证的是整个主机名字符串的长度在255个字符以内。

并列多个环视,并列的先后顺序无所谓:查找这样的起始位置,它之后是数字字符串,不过不能以999开头。

re.search(r"^(?=\d+)(?!999)", "1234") != None  # True
re.search(r"^(?=\d+)(?!999)", "abcd") != None  # False
re.search(r"^(?=\d+)(?!999)", "9991234") != None  # False

将若干个环视作为多选分支排列在多选结构中。比如想找到这样的位置,它之后要么不是数字字符,要么是一个数字字符和一个非数字字符。总的环视就是((?!\d)|(?=\d\D))。

嵌入条件

回溯引用条件只在前面的子表达式搜索取得成功的情况下才允许使用一个表达式。

用来定义这种条件的语法是(?(backreference)true-regex),其中?表明这是一个条件,括号里的backreference是一个回溯引用,true-regex是一个只在backreference存在时才会被执行的子表达式。

例子:需要把一段文本里的<IMG>标签全都找出来,如果某个<IMG>标签是一个链接(被括在<A>和</A>标签之间)的话,还要把整个链接标签匹配出来。

否则表达式只在给定的回溯引用不存在(也就是条件没有得到满足)时才会被执行。用来定义这种条件的语法是(?(backreference)true-regex|false-regex)

下面的电话号码只有第1和第2个是合法的,我们想匹配它们:

123-456-7890

(123)456-7890

(123)-456-7890

(123-456-7890

1234567890

123 456 7890

正则表达式:(\()?\d{3}(?(1)\)|-)\d{3}-\d{4}

(\()?匹配一个可选的左括号,(?(1)\)|-)是一个回溯引用条件,它将根据条件是否得到满足而去匹配)或-:如果(1)存在,\)必须被匹配;否则,-必须被匹配。这样一来,只有配对出现的括号才会被匹配;如果没有使用括号或括号不配对,电话号码中的区号和其余数字之间的-分隔符必须被匹配。

前后查找条件只在一个向前查找或向后查找操作取得成功的情况下才允许一个表达式被使用。

==例子没看懂==

匹配模式

常用的匹配模式:不区分大小写模式、单行模式、多行模式、注释模式。

不区分大小写模式有两种办法指定:以模式修饰符指定,或者以预定义的常量作为特殊参数传入来指定。

模式修饰符即模式名称对应的单个字符,使用时将其填入特定结构(?modifier)中(modifier为模式修饰符),嵌在正则表达式的开头。比如不区分大小写的匹配模式对应的模式修饰符是i。

模式修饰符写在最开头表示整个正则表达式都指定此模式,如果出现在中间,则表示此模式从这里开始生效;如果出现在某个括号内,那么它的作用范围只限于括号内部。Python的情况不同,不管出现在哪个位置,都对整个正则表达式生效。

有另一类失效修饰符,用来终止某种模式的作用范围,(?-modifier)。不过Python不支持这种写法。

这个表达式几乎可以原封不动的用在任何语言中,只有JavaScript例外。

# 以模式修饰符指定
re.search(r"(?i)the", "The") != None  # True

第二种方式是使用预定义的常量作为参数传入正则函数。比如Python是Re类的静态成员re.IGNORECASE

re.compile(r"the", re.I).search("THE") != None  # True
# 或者re.search(r"(?i)the", "THE", re.I) != None

点号不能匹配换行符。单行模式下所有文本似乎只在一行里,换行符变成了普通字符,点号可以匹配。

单行模式对应的模式修饰符是s。Python中的预定义常量是re.S和re.DOTALL。

单行模式影响的是点号的匹配规则。

多行模式影响是的^和$的匹配规则。默认模式下,它们匹配的是整个字符串的起始位置和结束位置,但在多行模式下,它们也能匹配字符串内部某一行文本的起始位置和结束位置。

模式修饰符是m,预定义常量是re.M和re.MULTILINE

multilineString = "1 line\nNot digit\n2 line"
lineBeginWithDigitRegex = r"(?m)^\d.*"
for line in re.findall(lineBeginWithDigitRegex, multilineString):
    print(line)
# 1 line
# 2 line

注释模式。许多语言支持使用(?#comment)的记法添加注释,comment就是注释的内容。模式修饰符是x。预定义常量是re.X和re.VERBOSE。

如果需要同时使用多种模式,只要将模式修饰符排列起来就可以了。比如(?mx)

或者将预定义常量用|组合起来。

Python的re还包含其他模式,列举2种

  • re.U或re.UNICODE:此模式下,\w, \d, \s等字符组简记法的匹配规则会发生改变,比如\w能匹配Unicode中的“单词字符”,包括中文字符,\d也能匹配1、2之类的全角数字字符,它有对应的模式修饰符u。

  • re.A或re.ASCII:Python3以上的版本中,正则表达式默认采用Unicode匹配规则,如果希望让\d, \w等字符组简记法恢复到ASCII匹配规则,可以使用此模式,它有对应的模式修饰符a。

Python中的匹配模式

常量
修饰符
解释

re.I re.IGNORECASE

i

不区分大小写模式

re.X re.VERBOSE

x

注释模式

re.M re.MULTILINE

m

多行模式

re.S re.DOTALL

s

单行模式

re.U re.UNICODE

u

Unicode模式

re.A re.ASCII

a

ASCII模式

转义

通常说的string中,string称为字符串文字,它是某个字符串的值在源代码中的表现形式。比如字符串文本\n,它包含\和n两个字符,意义是一个换行符。在生成字符串时,应当进行“字符串转义”,才能准确识别字符串文字中\n的意思。

在源代码中看到的“正则表达式”regex,其中的regex称为正则表达式文字,是正则表达式的表现形式。比如正则表达式\d,其正则文字包含\和d两个字符,它的意义是匹配数字字符的字符组简记法。在生成正则表达式时应当进行“正则转义”,才能将正则文字中的\d识别为字符组简记法。

不少正则表达式都是以字符串形式提供的。字符串文字首先必须经过字符串转义,才能得到真正的字符串,这个字符串作为正则文字,经过正则转义,最终得到正则表达式。

\\\\经过字符串转义和正则转义,得到的正则表达式就是一个\

和可以不加转义字符,而\b必须加,不然会被解析为退格符。最保险的办法是:正则表达式中的每一个\,在字符串文字中都要写成\\。

如果希望正则表达式是怎样的,正则文字中就怎样写,有两种办法:第一,使用原生字符串,也就是完全忽略字符串转义的特殊字符串。第二,直接使用正则文字,在Ruby和JavaScript中可以这么做。

常用结构的转义

结构
记法
转义
说明

字符组

[]

\[]

只对开方括号转义

.

\.

-

\-

[a\-b]等价于[-ab]

量词

* + ?

\* \+ \?

*? +? ??

\*\? \+\? \?\?

{m,n}

\{m,n}

只对开花括号转义

括号

(...)

\(...\)

开闭括号都需要转义

多选结构

`

`

|

括号和多选结构

`(...

...)`

`(...

断言

^ $

\^ \$

替换引用

$num

\$或$$

在替换的replacement字符串中转义

消除元字符特殊含义的函数:re.escape(text)

在字符组内部,只有三个字符需要转义

  • 闭方括号]

  • 横线-,如果紧跟在开方括号之后也可以不用转义

  • ^,如果不是紧跟在开方括号之后也可以不用转义

正则表达式的处理形式有函数式处理和面向对象式处理。

# 函数式
re.findall(r"\d+", "123 45 6")
# 面向对象式
digitRegex = re.compile(r"\d+")
re.findall(digitRegex, "123 45 6")
#或digitRegex.findall("123 45 6")

正则表达式中的优先级

优先级
组合
说明

1

(regex)

整个括号内的子表达式成为单个元素

2

* ? +

限定之前紧邻的元素

3

abc

普通拼接,元素相继出现

4

a|bc

多选结构

Unicode

在Unicode出现之前,各种系统的文字编码是各不相同的,比如中国大陆使用的是GB2312,中国台湾使用的是Big5,美国使用的是ASCII(ISO-8859-1),日本使用的是Shift JIS等。这些编码系统的作用,都是给每个字符分配一个数字(码值)作为编码,然后进行码值-字符和字符-码值的双向映射操作。

Unicode由两大部分组成,一部分是UCS(Universal Character Set),它定义了一个广阔的空间,能把各种不同语言中的字符全部装进去,为每个字符分配唯一的码值(Unicode中的术语是Code Point)。另一部分是UTF(Unicode Transformation Format),它定义了UCS中的每个码值以什么样的方式来传输(和存储)。

我们通常谈论的“Unicode编码”,严格来说是指UCS,也可以叫“字符集”,它关心每个字符对应的码值是多少。比如中文字符“正”的码值是十进制的27491,十六进制的表示是6B63。讨论Unicode时,常用的表示法是U+6B63.

码值只是概念,要变成字节才能够读取、存储、传输,所以要用到UTF。如果采用如今常用的UTF-8编码,它需要用3字节:E6 AD A3。

UCS空间设计的很大,码值从0到1114111,一共可以容纳超过100万个字符。整个UCS又可以分为17个“平面”(Planes),每个平面包含65536个码值。

常见的正则表达式的文档都是关于英文(ASCII字符)的,如果依照处理ASCII字符的方式处理中文字符,有可能出错。如果我们希望匹配“正”(GBK码值D5FD)或者“则”(GBK码值是D4F2),很自然会想到使用字符组[正则]。

GBK编码环境下的字符组会出现错误匹配(编码环境指的是源代码和它所处理的数据文本采用的编码):

# 能够匹配
re.search(r"[正则]", "遭遇") != None  # True
# 匹配的结果
repr(re.search(r"[正则]", "遭遇").group(0))  # '\xd4'
# 清楚地观察
re.compile(r"[正则]", re.DEBUG)

虽然用了GBK编码,每个中文字符用2字节表示,但是正则表达式处理时识别不出这是“2个多字节字符构成的字符组”,而将其视为“4个单字节字符构成的字符组”。如果按照字节来匹配,“正则”和“遭遇”恰好都有D4这个字节,所以这就是匹配结果。

Unicode编码环境下的字符组也会出现同样的错误匹配。(Python2下只要没有显式指定字符串为Unicode字符串,即便代码文件使用的是Unicode格式,仍然会按照单个字节的方式来处理)

如果我们把正则表达式和字符串都显式指定为Unicode模式,就可以彻底解决问题了。Python2的源代码的顶端要加上编码说明uft-8。

# 不能够匹配
re.search(ur"[正则]", u"遭遇") != None  # True
# 清楚地观察
re.compile(ur"[正则]", re.DEBUG)

Python3中字符串默认采用Unicode编码,所以不会出现上述问题。

GBK编码作为常见的中文编码,使用确实很多,但在GBK编码环境下使用正则表达式,却可能遇到非常奇怪的现象,因为正则表达式处理程序一般只能准确识别Unicode字符。所以不推荐使用GBK编码。

如果使用正则表达式处理含有多字节字符的文本,能使用Unicode编码环境就尽量使用。

Unicode与字符组简记法

正则表达式里的\d匹配数字字符,\w匹配单词字符,\s匹配空白字符。一般来说,数字字符就是[0-9],单词字符就是[0-9a-zA-Z],空白字符则包括空格、回车等字符,但这只是ASCII编码中的情况,在Unicode编码中并非如此。

因为涵盖了多种语言和字符,所以在Unicode编码中,全角数字也算作“数字字符”,可以由\d匹配;中文字符,也可以算作“单词字符”;中文的全角空格(码值为30FF),也可以算作“空白字符”。所以,如果在Python2中指定了正则表达式使用Unicode模式(最简单的方式就是在正则表达式的开头指定模式修饰符(?u)),\d, \w, \s就能匹配全角数字、中文字符、全角空格。对于这种情况,可以称为Unicode匹配规则,之前ASCII编码中的匹配,称为ASCII匹配规则。Python3默认采用Unicode匹配规则,可以在表达式最开始用(?a)指定ASCII模式。

Emoji

最全的Emoji列表:http://www.unicode.org/Public/emoji/5.0/emoji-data.txt

Python有个Emoji包,专门处理Emoji。

import emoji
# 点赞的手势
print(emoji.emojize('Python is :thumbs_up_sign:'))

匹配原理

正则表达式采用了一种特殊的理论模型:有穷自动机(Finite Automata,也叫有穷状态自动机,finite-state machine)。这种机器具备有限个状态,可以根据不同的条件在状态之间转移。

严格说起来,有穷自动机必须满足4个条件:

  • 具有有限多个状态

  • 有一套状态转移函数(或者叫规则)

  • 有一个开始状态

  • 有一个或多个最终状态

正则表达式所使用的理论模型就是有穷自动机,其具体实现称为正则引擎(Regex Engine)。用正则表达式处理字符串,首先需要生成自动机(很多语言中使用正则表达式之前都要编译正则对象),之后,无论输入什么字符串,正则引擎都只需要老老实实地在状态之间游走。

正则表达式的常见操作

提取:这里面的例子没有仔细看,有点复杂

验证

替换

切分

正则表达式的优化建议

1.使用缓存

使用Python的re.compile将正则表达式保存起来,反复使用,避免重复编译。

2.尽量准确地表达意图

3.避免重复匹配

4.独立出文本和锚点

常用的操作符模式总结

模式
描述

^

匹配字符串的开头,如^abc表示abc且在一个字符串的开头

$

匹配字符串的结尾,如abc$表示abc且在一个字符串的结尾

.

匹配任意字符,除了换行符

[...]

可以理解为"或"。对单个字符给出取值范围,例如[amk]匹配'a','m'或'k'

[^...]

不在[]中的字符:[^abc]匹配除了a,b,c之外的字符

re*

前一个字符0次或无限次扩展,如abc* 表示 ab、abc、abcc、abccc等

re+

前一个字符1次或无限次扩展 ,如abc+ 表示 abc、abcc、abccc等

re?

前一个字符0次或1次扩展 ,如abc? 表示 ab、abc

re{n}

精确匹配前面的表达式n次,例如o{2}表示匹配"oo"

re{n,}

匹配前面的表达式n次及以上

re{n,m}

匹配前面的表达式n到m次(含m),如ab{1,2}c表示abc、abbc

a|b

匹配a或b,如abc|def表示 abc、def

(re)

对正则表达式分组并记住匹配的文本,内部只能使用|操作符 ,如(abc)表示abc,(abc|def)表示abc、def

\w

匹配字母数字及下划线,等价于[A‐Za‐z0‐9_]

\W

匹配非字母数字及下划线

\s

匹配任意空白字符,等价于[\t\n\r\f]

\S

匹配任意非空字符

\d

匹配任意数字,等价于[0-9]

\D

匹配任意非数字

\A

匹配字符串开始

\Z

匹配字符串结束,如果是存在换行,只匹配到换行前的结束字符串。

\z

匹配字符串结束

\G

匹配最后匹配完成的位置。

\b

匹配一个单词边界,单词指的是数字、下划线或者字母。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。'\b99\b'可以匹配到'$99', '99',而不会匹配到'299'。

\B

匹配非单词边界。'er\B' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。

Python正则表达式re库的使用

常用函数说明

  • 调用方式:import re

  • re库采用raw string类型表示正则表达式,表示为:r'text',raw string是不包含对转义符再次转义的字符串;

re库的主要功能函数:

  • re.search() 在一个字符串中搜索匹配正则表达式的第一个位置,返回match对象

    • re.search(pattern, string, flags=0)

    • 使用group(num) 或 groups() 匹配对象函数来获取匹配表达式

    • 可以用来进行数据验证,在正则表达式两端加上\A和\Z,并判断返回值是否为None

  • re.match() 从一个字符串的开始位置起匹配正则表达式,返回match对象

    • re.match(pattern, string, flags=0)

    • 使用group(num) 或 groups() 匹配对象函数来获取匹配表达式

  • re.findall() 搜索字符串,以列表类型返回全部能匹配的子串

    • re.findall(pattern, string, flags=0)

  • re.split() 将一个字符串按照正则表达式匹配结果进行分割,返回列表类型

    • re.split(pattern, string, maxsplit=0, flags=0)

    • maxsplit是分隔次数,maxsplit=1 分隔一次,默认为 0,不限制次数。 如果是负数,表示不做任何切分。

  • re.finditer() 搜索字符串,返回一个匹配结果的迭代类型,每个迭代元素是match对象

    • re.finditer(pattern, string, flags=0)

  • re.sub() 在一个字符串中替换所有匹配正则表达式的子串,返回替换后的字符串

    • re.sub(pattern, repl, string, count=0, flags=0)

    • repl : 替换的字符串,也可为一个函数。 例如def capitalize(match): return match.group(1).upper+match.group(2).lower()

    • count : 模式匹配后替换的最大次数,默认 0 表示替换所有的匹配。

  • 参数说明

    • pattern是匹配的正则表达式

    • string是要匹配的字符串

    • flags : 正则表达式使用时的控制标记:

      • re.I --> re.IGNORECASE : 忽略正则表达式的大小写,[A‐Z]能够匹配小写字符

      • re.M --> re.MULTILINE : 正则表达式中的^操作符能够将给定字符串的每行当作匹配开始

      • re.S --> re.DOTALL : 正则表达式中的.操作符能够匹配所有字符,默认匹配除换行外的所有字符

Match对象常用的方法和属性

方法
描述
注释

start(n)

pos

end(n)

endpos

group(n)

groups()

span(n)

re

lastindex

string

expand(str)

re库的另一种等价用法(编译)

  • regex = re.compile(pattern, flags=0):将正则表达式的字符串形式编译成正则表达式对象, 供 match() 和 search() 等函数使用。

  • re.compile()还可以用来观察某个正则表达式的详细信息,方法是指定第二个参数为re.DEBUG。如果遇到复杂的表达式,或者不确定某个表达式的意义,可以通过它来观察。

re.compile("ab|cd", re.DEBUG)
BRANCH
  LITERAL 97
  LITERAL 98
OR
  LITERAL 99
  LITERAL 100
re.compile('ab|cd', re.DEBUG)

缩进表示了表达式各结构的层级关系,而字符本身则显示为其码值的十进制表示(比如字符a的码值是十进制的97)。

re 库的贪婪匹配和最小匹配

  • .* Re库默认采用贪婪匹配,即输出匹配最长的子串

  • *? 只要长度输出可能不同的,都可以通过在操作符后增加?变成最小匹配

条件匹配

Python的正则表达式支持条件匹配。语法是(?(id/name)yes-pattern|no-pattern)。其中id/name是对应捕获分组的名称或者编号,如果该捕获分组成功匹配文本,则后续匹配交由yes-pattern来完成,否则交由no-pattern来完成。no-pattern可以省略。

假设我们需要验证价格:如果前面没有美元符号$,则价格只能包含整数部分,否则还应当包含小数点和两位小数。

regex = r"^(\$)?[0-9]+(?(1)\.[0-9]{2}|)$"
re.search(regex, "34") != None   # True
re.search(regex, "12.00") != None   # False
re.search(regex, "$34") != None   # False
re.search(regex, "$12.00") != None   # True

实用小例子

1.删除文本中的特殊符号

def filter_str(sentence):
  remove_nota = u'[’·°–!"#$%&\'()*+,-./:;<=>?@,。?★、…【】()《》?“”‘’![\\]^_`{|}~]+'
  sentence = re.sub(remove_nota, '', sentence)
  remove_punctuation_map = dict((ord(char), None) for char in string.punctuation)
  sentence = sentence.translate(remove_punctuation_map)
  return sentence.strip()

remove_nota列举了一些特殊符号,主要是中文的。接着用re.sub函数替换文本中的特殊符号为空值。

string.punctuation是英文的标点符号,remove_punctuation_map将这些标点符号转换成一个字典,key是符号,value是None。而translate()方法是根据参数给出的表转换字符串的字符,也就是说把文本中和key相同的字符替换成value。因为这里的value是None,也就是空值了。

给个例子:

>>> filter_str('今天心情很好!@#$%^~*&')
'今天心情很好'

另外,附加福利,如果只想去掉文本末尾的括号要怎么做呢?非常简单:

re.sub(r'([(].*?[)])$', '', name)

正则表达式前面的r表示原始字符串,比如r'\t'等价于'\t'。

先来看这个表达式的开头和结尾:'()$',还记得吗?

'(re)'表示对正则表达式分组并记住匹配的文本。'$'表示匹配字符串的末尾。小括号里面有两个分隔的中括号,分别是'[(]'和'[)]',也就是表示匹配左括号和右括号,这里换成'('和')'也是一样的。中间还有一个'.*?',表示匹配任意字符0次或多次,但是在能使整个匹配成功的前提下使用最少的重复(非贪婪匹配)。

关于贪婪和非贪婪可以看看这个例子:

>>> s="This is a number 234-235-22-423"
>>> r=re.match(".+(\d+-\d+-\d+-\d+)",s)
>>> r.group(1)
'4-235-22-423'
>>> r=re.match(".+?(\d+-\d+-\d+-\d+)",s)
>>> r.group(1)
'234-235-22-423'

正则表达式模式中使用到通配字,那它在从左到右的顺序求值时,会尽量“抓取”满足匹配最长字符串,在我们上面的例子里面,“.+”会从字符串的起始处抓取满足模式的最长字符,其中包括我们想得到的第一个整型字段中的大部分,“\d+”只需一位字符就可以匹配,所以它匹配了数字“4”,而“.+”则匹配了从字符串起始到这个第一位数字4之前的所有字符。

解决方式:非贪婪操作符“?”,这个操作符可以用在"*","+","?"的后面,要求正则匹配的越少越好。

去掉文本首尾的特殊符号以及指定字符:

def REstrip(text, param=' '):
    # 去掉首尾的param
    text0 = ''
    i = 0
    remove_nota = u'[’·!"#$%&\'()*+,-./:;<=>?@,。?★、…【】()《》?“”‘’![\\]^_`{|}~]+'
    while text0 != text or i == 0:
        i += 1
        text0 = text
        text = text.strip().strip(string.punctuation)
        text = text.strip(remove_nota)
        mo = re.compile(r'^([' + str(param) + r']*)(.*?)([' + str(param) + ']*)$')
        result = mo.search(text)
        text = result.group(2)
    return text

这里用了一个循环,是为了保证去掉首尾的所有特殊字符,防止遗漏。

2.匹配文本中是否有某个特殊文本

寻找文本中出现的所有英文字母:

name = "abc 123 def 456"
re.findall(re.compile("[a-zA-Z]"), name)
# ['a', 'b', 'c', 'd', 'e', 'f']

找到文本中出现的第一个英文字母:

re.search("[a-zA-Z]", name)
# <re.Match object; span=(0, 1), match='a'>
re.search("[a-zA-Z]", name).start()
# 0
re.search("[a-zA-Z]", name).span()
# (0, 1)

寻找关键词在文本中出现的所有起始位置:

name = "123123"
keyword = "123"
start_list = [m.start() for m in re.finditer(keyword, name)]
start_list
# [0, 3]

如果只想要第一个起始位置,可以用:

name.find(keyword)
# 0

3.在中英文字符之间加上空格:

name = "今天真开心happy快乐"
name = re.sub('([A-Za-z]+)([\u4e00-\u9fa5]+)', r'\1 \2', name)
name = re.sub('([\u4e00-\u9fa5]+)([A-Za-z]+)', r'\1 \2', name)
name
# '今天真开心 happy 快乐'

两个表达式是类似地,只不过第一个表达式是在英文和中文字符之间加上空格,第二个表达式是在中文和英文字符之间加上空格。我们来看第一个表达式。第一个()是匹配1个或多个英文,第二个()是匹配1个或多个中文字符,r'\1 \2'是匹配第1个分组和第2个分组,且中间加了空格。

4.自定义分隔符

比如,以数字作为分隔符:

name = "我有2只眼睛1个鼻子"
re.split('\d', name)
# ['我有', '只眼睛', '个鼻子']
re.split('(\d)', name)
# ['我有', '2', '只眼睛', '1', '个鼻子']

加上()会使得分割后的列表保留数字,不加的话就不会保留。

以数字和空格作为分隔符:

name = "我有1只松鼠 2条狗  66只猫 还有鱼7 信不信"
re.split('(?<=\d)\s+|\s+(?=\d)', name)
# ['我有1只松鼠', '2条狗', '66只猫 还有鱼7', '信不信']

看到结果是:有空格+数字,或者数字+空格的地方都被分隔开了。且分隔后将空格去掉,数字保留。

要理解这个表达式,首先需要理解几个概念:

# 前瞻:
exp1(?=exp2) 查找exp2前面的exp1
# 后顾:
(?<=exp2)exp1 查找exp2后面的exp1
# 负前瞻:
exp1(?!exp2) 查找后面不是exp2的exp1
# 负后顾:
(?<!exp2)exp1 查找前面不是exp2的exp1

例如:

re.sub('(?<=中国)人', 'rr', '中国人') # 匹配中国人中的人,将其替换为rr,结果为 中国rr
re.sub('(?<=中国)人', 'rr', '法国人') # 结果为 法国人,因为人前面不是中国,所以无法匹配到

所以(?<=\d)\s+是查找数字后面的空格(可以是多个),\s+(?=\d)是查找数字前面的空格(可以是多个)。

再看看另一个类似的语法?:

()表示捕获分组,()会把每个分组里的匹配的值保存起来,使用$n(n是一个数字,表示第n个捕获组的内容)
(?:)表示非捕获分组,和捕获分组唯一的区别在于,非捕获分组匹配的值不会保存起来

举例1

re.findall('industr(?:y|ies)', 'industriesy')
# 等价于re.findall('industry|industries', 'industriesy')
# ['industries']
re.findall('industr(y|ies)', 'industriesy')
# ['ies']

举例2:数字格式化

# 数字格式化例如 1,123,000
re.sub('\B(?=(?:\d{3})+(?!\d))', ',', '1234567890')
# 结果:1,234,567,890,匹配的是后面是3*n个数字的非单词边界(\B)

参考资料

https://www.runoob.com/regexp/regexp-syntax.html

Previous预处理Next[1-2]文本预处理

Last updated 3 years ago

Was this helpful?

《Speech and Language Processing》