Python正则表达式-为什么要用原始字符串

本文为《爬着学Python》系列第六篇文章。
本文也算是系列教程的第二篇不规则更新。


在讨论Python正则表达式之前,我想先说说Python的原始字符串(Raw String)。我建议各位在Python正则式的pattern中一律使用原始字符串,本文的目的就在于讨论为什么要用原始字符串以及使用过程中需要注意的事项。

什么是原始字符串

原始字符串的Python提供的一种内置的用于简化转义的字符串形式。用法很简单,在定义字符串时加个r或者R就可以r'string'

>>> a = r'hello'
>>> type(a)
<class 'str'>

可以看到,即使我们把a定义成原始字符串,但是它的类型依然是字符串。而且不仅如此

>>> a = r'hello'
>>> b = 'hello'
>>> a is b
True

我们看到如果我们再定义一个普通字符串,他们在内存中引用的是同一个对象。目前,我们基本可以推测,所谓原始字符串,其实质就是普通字符串

既然一样,那么问题来了,那要它何用呢?

原始字符串的作用

可以说,原始字符串就是为了Python正则表达式而存在的,可以说也是Python的语法糖。我们先试试看最常见的例子,如何在Python正则中匹配\这个符号呢。下文中除特殊说明待处理的原字符串都默认设置为a\b\/c/d

>>> import re
>>> BASE = 'a\b\/c/d'
>>> print(BASE)
\/c/d

我们注意到输出这个字符串时,\b作为转义字符正常工作着,本来应该输出的a被退格了。

现在我们要尝试匹配BASE中的\该如何做呢?

>>> re.findall('\', BASE)
  File "<stdin>", line 1
    re.findall('\', a)
                     ^
SyntaxError: EOL while scanning string literal

上面的这个做法显然是在卖萌,程序是必然会报错的,原因在于\'会被转义为',而且我们知道

>>> re.findall('', BASE)
['', '', '', '', '', '', '', '']

也就是说,转义后的'是作为字符串的内容,不能作为标识字符串结束的边界。

正确的做法是

>>> re.findall('\\\\', BASE)
['\\']

结果是我们匹配到了一个\,也就是说,待处理字符串中作为转义字符使用的\是匹配不到的。我们为了匹配一个\,需要用上四个\

那为什么我们的pattern需要四个\呢?原因在于

>>> print('\\\\')
\\

我们的pattern字符串'\\'会转义成\,于是'\\\\'在正则匹配函数中先被理解为'\\',而'\\'用来匹配待处理字符串,则再一次被理解为用\来匹配字符串。

也就是说,正则表达式中进行了两次转义。第一次将字符串转义成pattern,在pattern匹配时则再次转义。为什么需要两次转义呢?

根本原因在于,字符串中的转义规则和正则表达式中的转义规则不一样。举例来说,'\b'在一般的字符串里面会转义成退格,在正则表达式里面会转义成单词边界。如果不约定好转义规则,'\b'到底应该转义成什么呢?于是Python中给出的解决办法是,先进行普通字符串的转义,将转义后的字符串作为参数传入正则匹配函数中,在正则匹配的过程中再进行正则表达式的转义。

这样的做法消除了歧义,但是带来了麻烦,造成了\泛滥的现象,这会给正则表达式带来极大的不便利。像用'\\\\'来匹配\的处理办法看上去太丑陋了。为了简化理解和操作,Python提供了原始字符串

>>> re.findall(r'\\', BASE)
['\\']

这使得我们可以使用匹配r'\\'来匹配\,这就显得容易理解多了,\在字符串中需要转义,这是我们能接受的。

原始字符串和普通字符串唯一的区别在于——原始字符串中的\都是默认经过转义的。

例外

但是,我要说但是,r'\'这种字符串还是不能出现的

>>> print(r'\')
  File "<stdin>", line 1
    print(r'\')
              ^
SyntaxError: EOL while scanning string literal

也就是说,在原始字符串中,\依然会对引号进行转义(看上去)

>>> print(r"\'")
\'
>>> print(repr(r"\'"))
"\\'"
>>> print("\\'")
\'

通过repr[1]函数可以清楚地看到,原始字符串中的\是经过自动转义的,因此先还原成\\,又在输出函数中再次转义成\

总之,想要用原始字符串只输出\几乎是不可能的,这也是不推荐大家在正则表达式以外的地方使用原始字符串的原因。我们可以简单理解为原始字符串是正则表达式中为了简化而专用的字符串形式。在正则式以外的地方能避免原始字符串带来的歧义就尽量不用。

测试

现在回到我们一直用的BASE,如何匹配其中的\/这两个字符呢?我们需要的输出是\/或者'\\/'

以下是我们的结论

>>> re.findall('\\\\/', BASE)
['\\/']
>>> re.findall(r'\\/', BASE)
['\\/']

链接

  1. 6.2. re — Regular expression operations — Python 3.6.2 documentation
  2. In context of Python Raw string - Stack Overflow

TODO


  1. repr函数

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,123评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,031评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,723评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,357评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,412评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,760评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,904评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,672评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,118评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,456评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,599评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,264评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,857评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,731评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,956评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,286评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,465评论 2 348

推荐阅读更多精彩内容