高级正则表达式技术(Python版)

  正则表达式是从信息中搜索特定的模式的一把瑞士军刀。它们是一个巨大的工具库,其中的一些功能经常被忽视或未被充分利用。今天我将向你们展示一些正则表达式的高级用法。

  举个例子,这是一个我们可能用来检测电话美国电话号码的正则表达式:


1

r‘^(1[-\s.])?(\()?\d{3}(?(2)\))[-\s.]?\d{3}[-\s.]?\d{4}$‘

  我们可以加上一些注释和空格使得它更具有可读性。


1

2

3

4

5

6

7

8

9

r‘^‘

r‘(1[-\s.])?‘ # optional ‘1-‘, ‘1.‘ or ‘1‘

r‘(\()?‘      # optional opening parenthesis

r‘\d{3}‘      # the area code

r‘(?(2)\))‘   # if there was opening parenthesis, close it

r‘[-\s.]?‘    # followed by ‘-‘ or ‘.‘ or space

r‘\d{3}‘      # first 3 digits

r‘[-\s.]?‘    # followed by ‘-‘ or ‘.‘ or space

r‘\d{4}$‘    # last 4 digits

  让我们把它放到一个代码片段里:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

import re

numbers = [ "123 555 6789",

            "1-(123)-555-6789",

            "(123-555-6789",

            "(123).555.6789",

            "123 55 6789" ]

for number in numbers:

    pattern = re.match(r‘^‘

                   r‘(1[-\s.])?‘           # optional ‘1-‘, ‘1.‘ or ‘1‘

                   r‘(\()?‘                # optional opening parenthesis

                   r‘\d{3}‘                # the area code

                   r‘(?(2)\))‘             # if there was opening parenthesis, close it

                   r‘[-\s.]?‘              # followed by ‘-‘ or ‘.‘ or space

                   r‘\d{3}‘                # first 3 digits

                   r‘[-\s.]?‘              # followed by ‘-‘ or ‘.‘ or space

                   r‘\d{4}$\s*‘,number)    # last 4 digits

    if pattern:

        print ‘{0} is valid‘.format(number)

    else:

        print ‘{0} is not valid‘.format(number)

  输出,不带空格:


1

2

3

4

5

123 555 6789 is valid

1-(123)-555-6789 is valid

(123-555-6789 is not valid

(123).555.6789 is valid

123 55 6789 is not valid

  正则表达式是 python 的一个很好的功能,但是调试它们很艰难,而且正则表达式很容易就出错。

  幸运的是,python 可以通过对 re.compile 或 re.match 设置 re.DEBUG (实际上就是整数 128) 标志就可以输出正则表达式的解析树。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

import re

numbers = [ "123 555 6789",

            "1-(123)-555-6789",

            "(123-555-6789",

            "(123).555.6789",

            "123 55 6789" ]

for number in numbers:

    pattern = re.match(r‘^‘

                    r‘(1[-\s.])?‘        # optional ‘1-‘, ‘1.‘ or ‘1‘

                    r‘(\()?‘             # optional opening parenthesis

                    r‘\d{3}‘             # the area code

                    r‘(?(2)\))‘          # if there was opening parenthesis, close it

                    r‘[-\s.]?‘           # followed by ‘-‘ or ‘.‘ or space

                    r‘\d{3}‘             # first 3 digits

                    r‘[-\s.]?‘           # followed by ‘-‘ or ‘.‘ or space

                    r‘\d{4}$‘, number, re.DEBUG)  # last 4 digits

    if pattern:

        print ‘{0} is valid‘.format(number)

    else:

        print ‘{0} is not valid‘.format(number)

  解析树


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

at_beginning

max_repeat 0 1

  subpattern 1

    literal 49

    in

      literal 45

      category category_space

      literal 46

max_repeat 0 2147483648

  in

    category category_space

max_repeat 0 1

  subpattern 2

    literal 40

max_repeat 0 2147483648

  in

    category category_space

max_repeat 3 3

  in

    category category_digit

max_repeat 0 2147483648

  in

    category category_space

subpattern None

  groupref_exists 2

    literal 41

None

max_repeat 0 2147483648

  in

    category category_space

max_repeat 0 1

  in

    literal 45

    category category_space

    literal 46

max_repeat 0 2147483648

  in

    category category_space

max_repeat 3 3

  in

    category category_digit

max_repeat 0 2147483648

  in

    category category_space

max_repeat 0 1

  in

    literal 45

    category category_space

    literal 46

max_repeat 0 2147483648

  in

    category category_space

max_repeat 4 4

  in

    category category_digit

at at_end

max_repeat 0 2147483648

  in

    category category_space

123 555 6789 is valid

1-(123)-555-6789 is valid

(123-555-6789 is not valid

(123).555.6789 is valid

123 55 6789 is not valid

  贪婪和非贪婪

  在我解释这个概念之前,我想先展示一个例子。我们要从一段 html 文本寻找锚标签:


1

2

3

4

5

6

7

import re

html = ‘Hello <a href="http://pypix.com" title="pypix">Pypix</a>‘

m = re.findall(‘<a.*>.*<\/a>‘, html)

if m:

    print m

  结果将在意料之中:


1

[‘<a href="http://pypix.com" title="pypix">Pypix</a>‘]

  我们改下输入,添加第二个锚标签:


1

2

3

4

5

6

7

8

import re

html = ‘Hello <a href="http://pypix.com" title="pypix">Pypix</a>‘ \

       ‘Hello <a href="http://example.com" title"example">Example</a>‘

m = re.findall(‘<a.*>.*<\/a>‘, html)

if m:

    print m

  结果看起来再次对了。但是不要上当了!如果我们在同一行遇到两个锚标签后,它将不再正确工作:


1

[‘<a href="http://pypix.com" title="pypix">Pypix</a>Hello <a href="http://example.com" title"example">Example</a>‘]

  这次模式匹配了第一个开标签和最后一个闭标签以及在它们之间的所有的内容,成了一个匹配而不是两个 单独的匹配。这是因为默认的匹配模式是“贪婪的”。

当处于贪婪模式时,量词(比如 * 和 +)匹配尽可能多的字符。

  当你加一个问号在后面时(.*?)它将变为“非贪婪的”。


1

2

3

4

5

6

7

8

import re

html = ‘Hello <a href="http://pypix.com" title="pypix">Pypix</a>‘ \

       ‘Hello <a href="http://example.com" title"example">Example</a>‘

m = re.findall(‘<a.*?>.*?<\/a>‘, html)

if m:

    print m

  现在结果是正确的。


1

[‘<a href="http://pypix.com" title="pypix">Pypix</a>‘, ‘<a href="http://example.com" title"example">Example</a>‘]

  前向界定符和后向界定符

  一个前向界定符搜索当前的匹配之后搜索匹配。通过一个例子比较好解释一点。

  下面的模式首先匹配 foo,然后检测是否接着匹配 bar


1

2

3

4

5

6

7

8

9

10

11

import re

strings = "hello foo",         # returns False

             "hello foobar"  ]    # returns True

for string in strings:

    pattern = re.search(r‘foo(?=bar)‘, string)

    if pattern:

        print ‘True‘

    else:

        print ‘False‘

  这看起来似乎没什么用,因为我们可以直接检测 foobar 不是更简单么。然而,它也可以用来前向否定界定。 下面的例子匹配foo,当且仅当它的后面没有跟着 bar


1

2

3

4

5

6

7

8

9

10

11

12

import re

strings = "hello foo",         # returns True

             "hello foobar",      # returns False

             "hello foobaz"]      # returns True

for string in strings:

    pattern = re.search(r‘foo(?!bar)‘, string)

    if pattern:

        print ‘True‘

    else:

        print ‘False‘

  后向界定符类似,但是它查看当前匹配的前面的模式。你可以使用 (?> 来表示肯定界定,(?<! 表示否定界定。

  下面的模式匹配一个不是跟在 foo 后面的 bar


1

2

3

4

5

6

7

8

9

10

11

12

import re

strings = "hello bar",         # returns True

             "hello foobar",      # returns False

             "hello bazbar"]      # returns True

for string in strings:

    pattern = re.search(r‘(?<!foo)bar‘,string)

    if pattern:

        print ‘True‘

    else:

        print ‘False‘

  条件(IF-Then-Else)模式

  正则表达式提供了条件检测的功能。格式如下:

(?(?=regex)then|else)

  条件可以是一个数字。表示引用前面捕捉到的分组。

  比如我们可以用这个正则表达式来检测打开和闭合的尖括号:


1

2

3

4

5

6

7

8

9

10

11

12

13

import re

strings = "<pypix>",    # returns true

             "<foo",       # returns false

             "bar>",       # returns false

             "hello" ]     # returns true

for string in strings:

    pattern = re.search(r‘^(<)?[a-z]+(?(1)>)$‘, string)

    if pattern:

        print ‘True‘

    else:

        print ‘False‘

  在上面的例子中,1 表示分组 (<),当然也可以为空因为后面跟着一个问号。当且仅当条件成立时它才匹配关闭的尖括号。

  条件也可以是界定符。

  无捕获组

  分组,由圆括号括起来,将会捕获到一个数组,然后在后面要用的时候可以被引用。但是我们也可以不捕获它们。

  我们先看一个非常简单的例子:


1

2

3

4

5

import re         

string = ‘Hello foobar‘

pattern = re.search(r‘(f.*)(b.*)‘, string)         

print "f* => {0}".format(pattern.group(1)) # prints f* => foo         

print "b* => {0}".format(pattern.group(2)) # prints b* => bar

  现在我们改动一点点,在前面加上另外一个分组 (H.*)


1

2

3

4

5

import re         

string = ‘Hello foobar‘

pattern = re.search(r‘(H.*)(f.*)(b.*)‘, string)         

print "f* => {0}".format(pattern.group(1)) # prints f* => Hello         

print "b* => {0}".format(pattern.group(2)) # prints b* => bar

  模式数组改变了,取决于我们在代码中怎么使用这些变量,这可能会使我们的脚本不能正常工作。 现在我们不得不找到代码中每一处出现了模式数组的地方,然后相应地调整下标。 如果我们真的对一个新添加的分组的内容没兴趣的话,我们可以使它“不被捕获”,就像这样:


1

2

3

4

5

import re         

string = ‘Hello foobar‘

pattern = re.search(r‘(?:H.*)(f.*)(b.*)‘, string)         

print "f* => {0}".format(pattern.group(1)) # prints f* => foo         

print "b* => {0}".format(pattern.group(2)) # prints b* => bar

  通过在分组的前面添加 ?:,我们就再也不用在模式数组中捕获它了。所以数组中其他的值也不需要移动。

  命名组

  像前面那个例子一样,这又是一个防止我们掉进陷阱的方法。我们实际上可以给分组命名, 然后我们就可以通过名字来引用它们,而不再需要使用数组下标。格式是:(?Ppattern) 我们可以重写前面那个例子,就像这样:


1

2

3

4

5

import re         

string = ‘Hello foobar‘

pattern = re.search(r‘(?P<fstar>f.*)(?P<bstar>b.*)‘, string)         

print "f* => {0}".format(pattern.group(‘fstar‘)) # prints f* => foo         

print "b* => {0}".format(pattern.group(‘bstar‘)) # prints b* => bar

  现在我们可以添加另外一个分组了,而不会影响模式数组里其他的已存在的组:


1

2

3

4

5

6

import re         

string = ‘Hello foobar‘

pattern = re.search(r‘(?P<hi>H.*)(?P<fstar>f.*)(?P<bstar>b.*)‘, string)         

print "f* => {0}".format(pattern.group(‘fstar‘)) # prints f* => foo         

print "b* => {0}".format(pattern.group(‘bstar‘)) # prints b* => bar         

print "h* => {0}".format(pattern.group(‘hi‘)) # prints b* => Hello

  使用回调函数

  在 Python 中 re.sub() 可以用来给正则表达式替换添加回调函数。

  让我们来看看这个例子,这是一个 e-mail 模板:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

import re         

template = "Hello [first_name] [last_name], \         

 Thank you for purchasing [product_name] from [store_name]. \         

 The total cost of your purchase was [product_price] plus [ship_price] for shipping. \         

 You can expect your product to arrive in [ship_days_min] to [ship_days_max] business days. \         

 Sincerely, \         

 [store_manager_name]"         

# assume dic has all the replacement data         

# such as dic[‘first_name‘] dic[‘product_price‘] etc...         

dic = {         

 "first_name" : "John",         

 "last_name" : "Doe",         

 "product_name" : "iphone",         

 "store_name" : "Walkers",         

 "product_price": "$500",         

 "ship_price": "$10",         

 "ship_days_min": "1",         

 "ship_days_max": "5",         

 "store_manager_name": "DoeJohn"

}         

result = re.compile(r‘\[(.*)\]‘)         

print result.sub(‘John‘, template, count=1)

  注意到每一个替换都有一个共同点,它们都是由一对中括号括起来的。我们可以用一个单独的正则表达式 来捕获它们,并且用一个回调函数来处理具体的替换。

  所以用回调函数是一个更好的办法:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

import re         

template = "Hello [first_name] [last_name], \         

 Thank you for purchasing [product_name] from [store_name]. \         

 The total cost of your purchase was [product_price] plus [ship_price] for shipping. \         

 You can expect your product to arrive in [ship_days_min] to [ship_days_max] business days. \         

 Sincerely, \         

 [store_manager_name]"         

# assume dic has all the replacement data         

# such as dic[‘first_name‘] dic[‘product_price‘] etc...         

dic = {         

 "first_name" : "John",         

 "last_name" : "Doe",         

 "product_name" : "iphone",         

 "store_name" : "Walkers",         

 "product_price": "$500",         

 "ship_price": "$10",         

 "ship_days_min": "1",         

 "ship_days_max": "5",         

 "store_manager_name": "DoeJohn"

}         

def multiple_replace(dic, text):

    pattern = "|".join(map(lambda key : re.escape("["+key+"]"), dic.keys()))

    return re.sub(pattern, lambda m: dic[m.group()[1:-1]], text)    

print multiple_replace(dic, template)

  不要重复发明轮子

  更重要的可能是知道在什么时候要使用正则表达式。在许多情况下你都可以找到 替代的工具。

  解析 [X]HTML

  Stackoverflow 上的一个答案用一个绝妙的解释告诉了我们为什么不应该用正则表达式来解析 [X]HTML。

  你应该使用使用 HTML 解析器,Python 有很多选择:

  后面两个即使是处理畸形的 HTML 也能很优雅,这给大量的丑陋站点带来了福音。

  ElementTree 的一个例子:


1

2

3

4

from xml.etree import ElementTree         

tree = ElementTree.parse(‘filename.html‘)         

for element in tree.findall(‘h1‘):         

   print ElementTree.tostring(element)

  其他

  在使用正则表达式之前,这里有很多其他可以考虑的工具

  感谢阅读!

  原文链接: Ajay Kumar N   翻译: 伯乐在线 - atupal

时间: 2024-10-11 01:09:28

高级正则表达式技术(Python版)的相关文章

《Python高级编程(第2版)》中文版PDF+英文版PDF+源代码

下载:https://pan.baidu.com/s/1W11Takw5zNYknzbGL_DZcw更多分享:https://pan.baidu.com/s/1yBmXcivRHZhepu2R1Ol11Q <Python高级编程(第2版)>中文版PDF+英文版PDF+源代码中文版PDF,421页,带书签目录:英文版PDF,536页,带书签目录:配套源代码.经典书籍,讲解详细. 基于Python 3.5版本进行讲解,通过13章的内容,深度揭示了Python编程的高级技巧.从Python语言及其社

《大话设计模式》Python版代码实现

上一周把<大话设计模式>看完了,对面向对象技术有了新的理解,对于一个在C下写代码比较多.偶尔会用到一些脚本语言写脚本的人来说,很是开阔眼界.<大话设计模式>的代码使用C#写成的,而在本人接触到的面向对象语言中,只对C++和Python还算了解,为了加深对各个模式的理解,我在网上下载了一个C++版的源代码,并根据自己的理解边读这本书边动手实践C++源代码,同时将其改写成了Python代码,算是一箭三雕吧. 由于这些代码的目的是展示各个设计模式而非完成一个具体的复杂任务,基于C++版本

《JavaScript高级程序设计(第3版)》.Nicholas.C.Zakas.扫描版.pdf

下载地址:网盘下载 内容简介 编辑 本书从最早期Netscape浏览器中的JavaScript开始讲起,直到当前它对XML和Web服务的具体支持,内容主要涉及JavaScript的语言特点.JavaScript与浏览器的交互.更高级的JavaScript技巧,以及与在Web应用程序中部署JavaScript解决方案有关的问题,如错误处理.调试.安全性.优化/混淆化.XML和Web服务,最后介绍应用所有这些知识来创建动态用户界面. 本书适合有一定编程经验的开发人员阅读,也可作为高校相关专业课程的教

python 版 quicksort 快排

今天看了下苹果xml 解析,写了个小demo 心想还是 在博客上写点东西吧,毕竟很久很久都没有上来了 先上个效果图把 接下来 看下 工程目录图吧 本demo 分两种解析模式,一是苹果自带的, 首先先看下苹果自带的吧,工程文件为 NoteXMLParser 文件 ,另一种解析模式 是 NotesTBXMLParser文件 NoteXMLParser.h 文件代码如下 : // // NoteXMLParser.h // TestXML // // Created by choni on 14-5-

正则表达式技术深入

正则表达式是从信息中搜索特定的模式的一把瑞士军刀.它们是一个巨大的工具库,其中的一些功能经常被忽视或未被充分利用.今天我将向你们展示一些正则表达式的高级用法. 举个例子,这是一个我们可能用来检测电话美国电话号码的正则表达式: r'^(1[-\s.])?(\()?\d{3}(?(2)\))[-\s.]?\d{3}[-\s.]?\d{4}$' 我们可以加上一些注释和空格使得它更具有可读性. r'^' r'(1[-\s.])?' # optional '1-', '1.' or '1' r'(\()?

《UNIX环境高级编程(第3版)》

<UNIX环境高级编程(第3版)> 基本信息 原书名:Advanced Programming in the UNIX Environment (3rd Edition) (Addison-Wesley Professional Computing Series) 原出版社: Addison-Wesley Professional 作者: (美)W. Richard Stevens    Stephen A. Rago 译者: 戚正伟 张亚英 尤晋元 出版社:人民邮电出版社 ISBN:9787

Linux环境下的高级隐藏技术

linux相关资料由兄弟连官方分享 摘要:本文深入分析了Linux环境下文件.进程及模块的高级隐藏技术,其中包括:Linux可卸载模块编程技术.修改内存映象直接对系统调用进行修改技术,通过虚拟文件系统proc隐藏特定进程的技术. 隐藏技术在计算机系统安全中应用十分广泛,尤其是在网络攻击中,当攻击者成功侵入一个系统后,有效隐藏攻击者的文件.进程及其加载的模块变得尤为重要.本文将讨论Linux系统中文件.进程及模块的高级隐藏技术,这些技术有的已经被广泛应用到各种后门或安全检测程序之中,而有一些则刚刚

【Python】《大话设计模式》Python版代码实现

<大话设计模式>Python版代码实现 上一周把<大话设计模式>看完了,对面向对象技术有了新的理解,对于一个在C下写代码比较多.偶尔会用到一些脚本语言写脚本的人来说,很是开阔眼界.<大话设计模式>的代码使用C#写成的,而在本人接触到的面向对象语言中,只对C++和Python还算了解,为了加深对各个模式的理解,我在网上下载了一个C++版的源代码,并根据自己的理解边读这本书边动手实践C++源代码,同时将其改写成了Python代码,算是一箭三雕吧. 由于这些代码的目的是展示各

JavaScript高级程序设计(第3版) 中文pdf扫描版 89M 高清下载

<JavaScript高级程序设计(第3版)>是JavaScript超级畅销书的最新版.ECMAScript5和HTML5在标准之争中双双胜出,使大量专有实现和客户端扩展正式进入规范,同时也为JavaScript增添了很多适应未来发展的新特性. <JavaScript高级程序设计>这一版除增加5章全新内容外,其他章节也有较大幅度的增补和修订,新内容篇幅约占三分之一. 全书从JavaScript语言实现的各个组成部分——语言核心.DOM.BOM.事件模型讲起,深入浅出地探讨了面向对象