一文搞定python正则表达式

概念&作用

正则表达式是一种用来描述文本模式的工具,它可以帮助你在字符串中查找匹配替换符合某种模式的文本。通过使用正则表达式,你可以定义一些规则,然后在文本中查找符合这些规则的内容。正则表达式可以用于各种编程语言和文本处理工具中,例如Python、JavaScript、Perl等。

python中的正则表达式

在python中,内置的 re 模块提供了对正则表达式的支持,这使得在 Python 中处理文本数据变得非常方便和高效。正则表达式在 Python 中非常有用,主要是因为re模块具有强大的模式匹配功能、文本处理的灵活性、替换和修改文本的能力以及Python 的易用性和广泛应用。

基础构成

字符

正则表达式中的字符可以是字母、数字或符号,它们代表自身字符。例如,a 匹配字符 "a"。

元字符

元字符是具有特殊含义的字符,它们不仅仅代表自身字符。一些常见的元字符包括

元字符 作用含义
. 匹配除换行符以外的任意字符。
^ 匹配字符串的开头。
$ 匹配字符串的结尾。
\d 匹配任意一个数字字符,相当于 [0-9]
\D 匹配任意一个非数字字符,相当于 [^0-9]
\w 匹配任意一个字母、数字或下划线字符,相当于 [a-zA-Z0-9_]
\W 匹配任意一个非字母、数字或下划线字符,相当于 [^a-zA-Z0-9_]
\s 匹配任意一个空白字符,包括空格、制表符、换行符等
\S 匹配任意一个非空白字符
\b 匹配单词边界,即字与空格之间的位置
\B 匹配非单词边界
* 匹配前一个字符的零个或多个。
+ 匹配前一个字符的一个或多个。
? 匹配前一个字符的零个或一个。
[] 匹配方括号内的任意一个字符。例如,[aeiou]匹配任意一个元音字母。
| 匹配两个或多个模式之一。
() 用于创建捕获组,可以对匹配的内容进行分组。
量词

量词用于指定匹配字符重复出现的次数,常见的量词包括:

量词 作用含义
{m} 精确匹配前一个字符出现 m 次
{m,n} 匹配前一个字符出现至少 m 次,最多 n 次
{m,} 匹配前一个字符出现至少 m 次
* 匹配前一个字符的零个或多个
+ 匹配前一个字符的一个或多个
? 匹配前一个字符的零个或一个
原始字符串

在Python中,r字符串是一种特殊的字符串表示形式,被称为原始字符串(raw string)。在原始字符串中,反斜杠 \ 不会被解释为转义字符,而是作为普通字符对待。这意味着在原始字符串中,反斜杠后面的字符会保持原样,不会被转义。

例如,在普通字符串中,要表示一个Windows文件路径,你需要使用双反斜杠来转义,如下所示:

path = "C:\\Users\\username\\Documents\\file.txt"

而在原始字符串中,你可以直接写成:

path = r"C:\Users\username\Documents\file.txt"

这使得处理一些特殊字符串,如正则表达式模式或文件路径,更加方便,因为你无需担心转义字符带来的影响。

re模块

Python 的正则表达式模块 re 提供了一系列功能,让你可以在字符串中进行模式匹配、查找和替换等操作。你提到的 re.match()re.search()re.findall()re.sub()re.compile() 是其中常用的几个函数。

re.match

re.match(pattern, string, flags=0)尝试从字符串的开头匹配一个模式。如果字符串开头匹配成功,则返回一个匹配对象;否则返回 None。这意味着它只匹配字符串的开头位置。

import re

pattern = r'hello'
string = 'hello world'
match_obj = re.match(pattern, string)
if match_obj:
    print("Match found:", match_obj.group())
else:
    print("No match found")

# Match found: hello
re.search

re.search(pattern, string, flags=0)re.match(pattern, string, flags=0)类似,但是它在整个字符串中搜索匹配项,返回第一个匹配到的对象。这意味着它可以匹配字符串的任意位置。

import re

pattern = r'world'
string = 'hello world'
search_obj = re.search(pattern, string)
if search_obj:
    print("Match found:", search_obj.group())
else:
    print("No match found")

# Match found: world
re.findall

re.findall(pattern, string, flags=0)在字符串中找到所有匹配项,并以列表的形式返回。它不同于 re.match()re.search(),它不返回匹配对象,而是直接返回匹配的字符串列表。

import re

pattern = r'lo'
string = 'hello world'
matches = re.findall(pattern, string)
print("Matches found:", matches)

# Matches found: ['lo']
re.sub

re.sub(pattern, repl, string, count=0, flags=0)用来在字符串中查找匹配项,并替换为指定的字符串。它返回替换后的新字符串。

import re

pattern = r'world'
replacement = 'universe'
string = 'hello world'
new_string = re.sub(pattern, replacement, string)
print("New string:", new_string)

# New string: hello universe
re.compile

re.compile(pattern, flags=0)是Python 中用于预编译正则表达式模式的函数。这个函数接受一个正则表达式作为参数,并返回一个正则表达式对象,这个对象可以被用来执行匹配操作。这个函数诸多优点:

  • 提高效率: 编译后的正则表达式对象可以在多次使用时提高匹配效率。因为编译后的对象已经将正则表达式模式编译为内部数据结构,避免了每次匹配都要重新解析模式的开销。
  • 可重用性: 编译后的正则表达式对象可以在多个地方使用,而不需要每次都重新编译正则表达式模式,提高了代码的可重用性。
  • 简化代码: 将常用的正则表达式模式预先编译,可以简化代码,使代码更加清晰易懂。
import re

# 编译正则表达式模式
pattern = re.compile(r'\d{3}-\d{3}-\d{4}')

# 使用编译后的对象进行匹配操作
result = pattern.search("John's phone number is 123-456-7890.")
if result:
   print("Phone number found:", result.group())
else:
   print("Phone number not found.")

# Phone number found: 123-456-7890
flag的可选值
  • re.IGNORECASEre.I: 忽略大小写匹配。
  • re.MULTILINEre.M: 多行匹配,使 ^$ 匹配字符串的每行的开头和结尾,而不是整个字符串的开头和结尾。
  • re.DOTALLre.S: 让 . 匹配任何字符,包括换行符。
  • re.UNICODEre.U: 使 \w, \W, \b, \B, \d, \D, \s, \SUnicode 字符集合匹配。
  • re.ASCII: 使 \w, \W, \b, \B, \d, \D, \s, \SASCII 字符集合匹配。
  • re.VERBOSEre.X: 忽略空白和注释,使得更易读的正则表达式能够被编写。

正/反向前瞻

正向前瞻(Positive Lookahead):正向前瞻用 (?=…) 表示,它指定了一个条件,在匹配位置的右侧必须满足这个条件才能匹配成功。但匹配的位置本身不会被包含在匹配结果中。

反向前瞻(Negative Lookahead):反向前瞻用 (?!…) 表示,它指定了一个条件,在匹配位置的右侧必须不满足这个条件才能匹配成功。同样,匹配的位置本身不会被包含在匹配结果中。

接下来演示一下,假设我们要匹配一个字符串中的所有数字,但是要排除包含小数点的数字。我们可以使用正向前瞻来实现这个匹配:

import re

# 匹配所有不包含小数点的数字
pattern = r'\d+(?!\.)'
text = "123 456 7.89 10"

matches = re.findall(pattern, text)
print(matches)

# ['123', '456', '10']

在这个示例中,r'\d+(?!\.)' 匹配的是一串数字 \d+,但是它的右侧不能跟着一个小数点 (?!\.),这样就排除了包含小数点的数字。

反向前瞻也可以用来排除某些特定情况的匹配。例如,假设我们要匹配所有不是以字母 "a" 开头的单词:

import re

# 匹配所有不以字母 "a" 开头的单词
pattern = r'\b(?!a)\w+\b'
text = "apple banana orange"

matches = re.findall(pattern, text)
print(matches)  
# ['banana', 'orange']

在这个示例中,r'\b(?!a)\w+\b' 匹配的是一个单词,但是它的左侧不能以字母 "a" 开头 (?!a),这样就排除了以 "a" 开头的单词 "apple"。

捕获组和非捕获组的概念

在正则表达式中,捕获组和非捕获组是用来对匹配的文本进行分组和提取的工具, 比如Django/Flask等框架的url路由匹配就是用了这个。
捕获组:捕获组用括号 ( ) 表示,它可以将匹配的文本分组并保存到一个单独的组中,以便后续引用或提取。捕获组可以在整个正则表达式中通过反向引用(backreference)或在匹配结果中进行访问。

非捕获组:非捕获组也用括号 ( ) 表示,但是在左括号后紧跟着一个问号和冒号 (?: ),它用于对文本进行分组,但不会在匹配结果中保存分组的内容,也不会创建一个新的捕获组。非捕获组通常用于提高正则表达式的效率,因为它不会占用额外的内存来存储匹配结果。

接下来演示一下, 假设我们有一个包含邮箱地址的字符串,我们想从中提取用户名和域名部分。我们可以使用捕获组来实现:

import re

# 匹配邮箱地址的正则表达式
pattern = r'(\w+)@(\w+\.\w+)'

text = "jeff@xmishu.com, alice@baidu.com"

# 使用捕获组提取用户名和域名
matches = re.findall(pattern, text)
for match in matches:
    username, domain = match
    print("Username:", username)
    print("Domain:", domain)

在这个示例中,(\w+)(\w+\.\w+) 分别是两个捕获组,用来提取用户名和域名。re.findall() 函数会返回一个列表,其中每个元素是一个包含用户名和域名的元组。通过遍历这个列表,我们可以轻松地获取用户名和域名部分。

实际应用

正则表达式在python应用中非常广泛,它能辅助我们高效地处理很多问题

  • 数据提取(网页数据采集等)
  • 文本处理(数据清洗、格式化入库等)
  • web表单中用户输入内容验证(邮箱密码格式验证等)。

注意事项

  • 预编译正则表达式:如果你要在多个地方使用相同的正则表达式,可以使用re.compile()预先编译它,以提高匹配效率,生产环境中很有用。
  • 选择最适合的函数:Python的re模块提供了多个函数用于正则表达式操作,如re.match()re.search()re.findall()等。根据需求选择最适合的函数,以避免不必要的性能开销。
  • 使用非贪婪匹配:在量词后面加上?可以将匹配模式变为非贪婪模式,尽可能少地匹配字符。例如,*?+???
  • 避免过度使用 .:.可以匹配任意一个字符,但在某些情况下可能会导致性能下降。如果可能,尽量使用更具体的字符集或模式来限制匹配范围。
  • 使用原始字符串:在编写正则表达式时,最好使用原始字符串(以r开头),以避免不必要的转义,提高代码可读性。
  • 使用字符类:字符类(如[0-9][a-zA-Z])比单个字符更有效率,因为它们允许正则引擎更快地确定是否匹配。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容