• 35.正则表达式


    正则表达式概述

    正则表达式,又称正规表示式、正规表示法、正规表达式、规则表达式、常规表示法(英语:Regular Expression,在代码中常简写为regex、regexp或RE),是计算机科学的一个概念。正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串。在很多文本编辑器里,正则表达式通常被用来检索、替换那些匹配某个模式的文本。

    Regular Expression的"Regular"一般被译为"正则"、"正规"、"常规"。此处的"Regular"即是"规则"、"规律"的意思,Regular Expression即"描述某种规则的表达式"之意。

    核心笔记:

    1、当我们完全讨论与字符串中模式有关的正则表达式时,我们会用术语 "matching"(匹配),指的是术语 pattern-matching(模式匹配)。

    2、在 Python专门术语中,有两种主要方法完成模式匹配:搜索(searching)和匹配(matching)

    3、搜索,即在字符串任意部分中查找匹配的模式,而匹配是指,判断一个字符串能否从起始处全部或部分的匹配某个模式

    4、搜索通过 search()函数或方法来实现,而匹配是以调用 match()函数或方法实现的。
    5、总之,当我们说模式的时候,我们全部使用术语“matching”(“匹配”);我们按照 Python 如何完成模式匹配的方式来区分“搜索”和“匹配”。

    下图展示了使用正则表达式进行匹配的流程:

    正则表达式的大致匹配过程是:依次拿出表达式和文本中的字符比较,如果每一个字符都能匹配,则匹配成功;一旦有匹配不成功的字符则匹配失败。如果表达式中有量词或边界,这个过程会稍微有一些不同,但也是很好理解的,看下图中的示例以及自己多使用几次就能明白。

    二、正则表达式使用的特殊符号和字符

    现在,我们来介绍最常用的元字符(metacharacters)——特殊字符和符号,正是它们赋予了正则表达式强大的功能和灵活性

    下面为正则表达式中常见的符号和字符

    1、普通字符和11个元字符:

    普通字符
    匹配自身
    abc
    abc
    .
    匹配任意除换行符" "外的字符(在DOTALL模式中也能匹配换行符
    a.c
    abc
    转义字符,使后一个字符改变原来的意思
    a.c;a\c
    a.c;ac
    *
    匹配前一个字符0或多次
    abc*
    ab;abccc
    +
    匹配前一个字符1次或无限次
    abc+
    abc;abccc
    ?
    匹配一个字符0次或1次
    abc?
    ab;abc
    ^
    匹配字符串开头。在多行模式中匹配每一行的开头 ^abc
    abc
    $
    匹配字符串末尾,在多行模式中匹配每一行的末尾 abc$
    abc
    | 或。匹配|左右表达式任意一个,从左到右匹配,如果|没有包括在()中,则它的范围是整个正则表达式
    abc|def
    abc
    def
    {} {m}匹配前一个字符m次,{m,n}匹配前一个字符m至n次,若省略n,则匹配m至无限次
    ab{1,2}c
    abc
    abbc
    []
    字符集。对应的位置可以是字符集中任意字符。字符集中的字符可以逐个列出,也可以给出范围,如[abc]或[a-c]。[^abc]表示取反,即非abc。
    所有特殊字符在字符集中都失去其原有的特殊含义。用反斜杠转义恢复特殊字符的特殊含义。
    a[bcd]e
    abe
    ace
    ade
     
    ()
    被括起来的表达式将作为分组,从表达式左边开始每遇到一个分组的左括号(,编号+1.
    分组表达式作为一个整体,可以后接数量词。表达式中的|仅在该组中有效。
    (abc){2}
    a(123|456)c
    abcabc
    a456c

     2、预定义字符集(可以写在字符集[...]中)

    d
    匹配任何数字:[0-9]
    ac
    a1c
    D
    非数字:[^d]
    aDc
    abc
    s
    匹配任何空白字符:[<空格> fv]
    asc
    a c
    S 非空白字符:[^s]
    aSc
    abc
    w
    匹配包括下划线在内的任何字符:[A-Za-z0-9_]
    awc
    abc
    W
    匹配非字母字符,即匹配特殊字符
    aWc
    a c
    A
    仅匹配字符串开头,同^ Aabc
    abc
    
    仅匹配字符串结尾,同$
    abc
    abc
    
    匹配w和W之间,即匹配单词边界。匹配一个单词边界,也就是指单词和空格间的位置。例如, 'er' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。 abc
    a!bc
    空格abc空格
    a!bc
    B
    [^]
    aBbc
    abc

    这里需要强调一下的单词边界的理解:

    w = re.findall('tina','tian tinaaaa')
    print(w)
    s = re.findall(r'tina','tian tinaaaa')
    print(s)
    v = re.findall(r'tina','tian#tinaaaa')
    print(v)
    a = re.findall(r'tina','tian#tina@aaa')
    print(a)
    执行结果如下:
    []
    ['tina']
    ['tina']
    ['tina']

    3、特殊分组用法:

    (?P<name>)
    分组,除了原有的编号外再指定一个额外的别名 (?P<id>abc){2}
    abcabc
    (?P=name)
    引用别名为<name>的分组匹配到字符串 (?P<id>d)abc(?P=id)
    1abc1
    5abc5
    <number>
    引用编号为<number>的分组匹配到字符串 (d)abc1
    1abc1
    5abc5

    分组就是用一对圆括号()括起来的正则表达式,匹配出的内容就表示一个分组。从正则表达式的左边开始看,看到的第一个左括号"("表示第一个分组,第二个表示第二个分组,依次类推,需要注意的是,有一个隐含的全局分组(就是0),就是整个正则表达式。

    命名分组

    命名分组就是给具有默认分组编号的组另外再给一个别名。命名分组的语法格式如下:

    (?P<name>正则表达式)    #name是一个合法的标识符

    (?P=name)通过命名分组名进行引用

    (?P=name) 字符P必须是大写的P,name表示命名分组的分组名

    (?P<name>)(?P=name) 引用分组的值匹配值必须与第一个分组匹配值相等才能匹配到

    例如:

    1)    引用前一个分组,前后值相同都是2,故能匹配到

    例如:

    import re
    print re.match(r'(?P<xst>d)(?P=xst)','22').groups()  #('2',)
    print re.match(r'(?P<xst>d)(?P=xst)','22').group()  #22 

    2)    引用前一个分组,前后值不相同分别为2和3,故不能匹配到

     例如

    import re
    print re.match(r'(?P<xst>d)(?P=xst)','23').group() #AttributeError: 'NoneType' object has no attribute 'group'

     后向引用

     正则表达式中,放在圆括号()中的表示是一个组。然后你可以对整个组使用一些正则操作,例如重复操作符。
    要注意的是,只有圆括号()才能用于形成组。""用于定义字符集。{}用于定义重复操作。
    当用()定义了一个正则表达式组后,正则引擎则会把被匹配的组按照顺序编号,存入缓存。这样我们想在后面对已经匹配过的内容进行引用时,就可以用"数字"的方式或者是通过命名分组进行"(?P=name)"进行引用。1表示引用第一个分组,2引用第二个分组,以此类推, 引用第n个组。而则引用整个被匹配的正则表达式本身。这些引用都必须是在正则表达式中才有效,用于匹配一些重复的字符串。

    前向肯定断言、后向肯定断言

    前向肯定断言的语法:

    (?=pattern)

     (?<=pattern) 前向肯定断言表示你希望匹配的字符串前面是pattern匹配的内容时,才匹配。

    后向肯定断言的语法:

    (?<=pattern)

     (?=pattern) 后向肯定断言表示你希望匹配的字符串的后面是pattern匹配的内容时,才匹配

    前后向断言同时使用

    需要注意的是,如果在匹配的过程中,需要同时用到前向肯定断言和后向肯定断言,那么必须将后向肯定断言写在正则语句的前面,前向肯定断言写在正则语句的后面,表示后向肯定模式之后,前行肯定模式之前

    前向否定断言、后向否定断言

    前向否定断言语法:

    (?!pattern)

     (?<!pattern) 前向否定断言表示你希望匹配的字符串的前面不是pattern匹配的内容时,才匹配.

    后向否定断言语法:

    (?<!pattern)

     (?!pattern) 后向否定断言表示你希望匹配的字符串后面不是pattern匹配的内容时,才匹配。

    注意:

    前向肯定(否定)断言括号中的正则表达式必须是能确定长度的正则表达式,比如w{3},而不能写成 w*或者w+或者w?等这种不能确定个数的正则模式符。

    数量词的贪婪模式与非贪婪模式

    正则表达式通常用于在文本中查找匹配的字符串。Python里数量词默认是贪婪的(在少数语言里也可能是默认非贪婪),总是尝试匹配尽可能多的字符;非贪婪的则相反,总是尝试匹配尽可能少的字符。例如:正则表达式"ab*"如果用于查找"abbbc",将找到"abbb"。而如果使用非贪婪的数量词"ab*?",将找到"a"。

     表达式 .* 的意思很好理解,就是单个字符匹配任意次,即贪婪匹配。
    表达式 .*? 是满足条件的情况只匹配一次,即懒惰匹配

    三、正则表达式

    re 模块: 核心函数和方法

     

    常见的正则表达式函数与方法

    re 模块的函数

    compile(pattern,flags=0) :对正则表达式模式 pattern 进行编译,flags 是可选标志符,并返回一个 regex 对象

    常用的flags有:

    编译标志让你可以修改正则表达式的一些运行方式。在 re 模块中标志可以使用两个名字,一个是全名如 IGNORECASE,一个是缩写,字母形式如 I

    re.S(DOTALL):使.匹配包括换行在内的所有字符。没有这个标志, "." 匹配除了换行外的任何字符。
    re.I(IGNORECASE):使匹配对大小写不敏感
    re.L(LOCALE):影响 "w, "W, "b, 和 "B,这取决于当前的本地化设置。cales 是 C 语言库中的一项功能,是用来为需要考虑不同语言的编程提供帮助的。
              做本地化识别(locale-aware)匹配,法语等
    re.M(MULTILINE):多行匹配,影响^和$。当本标志指定后, "^" 匹配字符串的开始和字符串中每行的开始。
              同样的, $ 元字符匹配字符串结尾和字符串中每行的结尾(直接在每个换行之前)。
    re.X(VERBOSE):该标志通过给予更灵活的格式以便将正则表达式写得更易于理解。当该标志被指定时,在 RE 字符串中的空白符被忽略,
             除非该空白符在字符类中或在反斜杠之后;这可以让你更清晰地组织和缩进 RE。它也可以允许你将注释写入 RE,这些注释会被引擎忽略;
              注释用 "#"号 来标识,不过该符号不能在字符串或反斜杠之后。
    re.U:根据Unicode字符集解析字符,这个标志影响w,W,,B

    re 模块的函数和 regex 对象的方法

    findall(pattern,string[,flags]): 在字符串 string 中查找正则表达式模式 pattern 的所有(非重复)出现的字串;返回一个匹配对象的列表
                        如果模式中存在一个或多个组,则返回组列表;如果模式有多个组,那么这将是一个元组列表。
                        结果中包含空的匹配项。
    
    finditer(pattern,string[, flags]): 和 findall()相同,但返回的不是列表而是迭代器;对于每个匹配,该迭代器返回一个匹配对象
    match(pattern,string, flags=0) :尝试用正则表达式模式 pattern 匹配字符串 string,flags 是可选标志符,如果匹配成功,则返回一个匹配对
    象;否则返回 None

    search(pattern,string, flags=0) :在字符串 string 中查找正则表达式模式 pattern 的第一次出现的字串,flags 是可选标志符,如果匹配成功,则返回
                      一个匹配对象;否则返回 None

    匹配对象的方法

    group(num=0) 或group([group1,...]:返回全部匹配对象,或指定编号是 num 的子组。返回匹配到的一个或者多个子组。如果是一个参数,那么结果就是一个字符串,
            如果是多个参数,那么结果就是一个参数一个item的元组。group1的默认值为0(将返回所有的匹配值).如果groupN参数为0,
            相对应的返回值就是全部匹配的字符串,如果group1的值是[1…99]范围之内的,那么将匹配对应括号组的字符串。
            如果组号是负的或者比pattern中定义的组号大,那么将抛出IndexError异常。如果pattern没有匹配到,
            但是group匹配到了,那么group的值也为None。如果一个pattern可以匹配多个,那么组对应的是样式匹配的最后一个。
            另外,子组是根据括号从左向右来进行区分的

    groups([default]) :返回一个包含全部匹配的子组的元组(如果没有成功匹配,就返回一个空元组)。
            返回一个包含所有子组的元组。Default是用来设置没有匹配到组的默认值的。Default默认是None,
    split(pattern,string, max=0): 根据正则表达式 pattern 中的分隔符把字符 string 分割为一个列表,返回成功匹配的列表,最多分割 max 次(默
    认是分割所有匹配的地方)。

    sub(pattern, repl, string, max=0) :把字符串 string 中所有匹配正则表达式 pattern 的地方替换成字符串 repl,如果 max 的值没有给出, 则对所有
    匹配的地方进行替换(另外,请参考 subn(),它还会返回一个表示替换次数的数值)。

    groupdict([default]):返回一个包含所有命名组的名字和子串的字典,default参数,用于给那些没有匹配到的组做默认值,它的默认值是None
                  返回匹配到的所有命名子组的字典。Key是name值,value是匹配到的值。参数default是没有匹配到的子组的默认值

    start([group]),end([group]):返回的是:被组group匹配到的子串在原字符串中的位置。如果不指定group或group指定为0,则代表整个匹配。
                如果group未匹配到,则返回 -1。对于指定的m和g,m.group(g)和m.string[m.start(g):m.end(g)]等效。
                注意:如果group匹配到空字符串,m.start(group)和m.end(group)将相等

    compile函数

    对正则表达式模式进行编译,返回一个正则表达式对象
    不是必须要用这种方式,但是在大量匹配的情况下,可以提升效率

     语法:

    re.compile(pattern,flags=0)

    pattern: 编译时用的表达式字符串。

    flags 编译标志位,用于修改正则表达式的匹配方式,如:是否区分大小写,多行匹配等。常用的flags有:

    标志
    含义
    re.S(DOTALL)
    使.匹配包括换行在内的所有字符
    re.I(IGNORECASE)
    使匹配对大小写不敏感
    re.L(LOCALE)
    做本地化识别(locale-aware)匹配,法语等
    re.M(MULTILINE)
    多行匹配,影响^和$
    re.X(VERBOSE)
    该标志通过给予更灵活的格式以便将正则表达式写得更易于理解
    re.U
    根据Unicode字符集解析字符,这个标志影响w,W,,B

    例子1:

    #!/usr/bin/env python
    #coding:utf-8
    
    import re
    patt = re.compile('foo')
    m = patt.match('food')
    print m.group()  #foo

     例子2:

    #!/usr/bin/env python
    #coding:utf-8
    
    import re
    
    tt = "Tina is a good girl, she is cool, clever, and so on..."
    rr = re.compile(r'w*oow*')  #匹配oo,无论oo前面或后面是否出现任何字符
    print(rr.findall(tt))#查找所有包含'oo'的单词

    用 match()匹配字符串

    match()函数尝试从字符串的开头开始对模式进行匹配。如果匹配成功,就返回一个匹配对象,而如果匹配失败了,就返回 None。

    注:这个方法并不是完全匹配。当pattern结束时若string还有剩余字符,仍然视为成功。想要完全匹配,可以在表达式末尾加上边界匹配符'$'

    匹配对象的 group() 方法可以用来显示那个成功的匹配

     re.match是用来进行正则匹配检查的方法,若字符串匹配正则表达式,则match方法返回匹配对象(Match Object),否则返回None(注意不是空字符串"")。

    匹配对象Macth Object具有group方法,用来返回字符串的匹配部分。

    语法:

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

    例子1:

    #!/usr/bin/env python
    #coding:utf-8
    
    import re
    m1 = re.match('foo','food') #成功匹配
    print m1  #<_sre.SRE_Match object at 0x000000000304A510>
    
    m2 = re.match('foo', 'seafood') #未能匹配
    print m2  #None

     例子2:

    #!/usr/bin/env python
    #coding:utf-8
    
    import re
    print(re.match('com','comwww.runcomoob').group())  #com
    print(re.match('com','Comwww.runcomoob',re.I).group())  #Com

    search() 在一个字符串中查找一个模式 (搜索与匹配的比较)

    1、search 和 match 的工作方式一样,不同之处在于 search 会检查参数字符串任意位置的地方给定正则表达式模式的匹配情况。

    如果搜索到成功的匹配,会返回一个匹配对象,否则返回 None
    2、search() 查找字符串中模式首次出现的位置,而不是尝试(在起始处)匹配。严格地说,search() 是从左到右进行搜索

    3、在字符串中查找正则表达式模式的第一次出现,如果匹配成功,则返回一个匹配对象;否则返回None

    #!/usr/bin/env python
    #coding:utf-8
    
    import re
    m1 = re.search('foo','food') #成功匹配
    print m1  #<_sre.SRE_Match object at 0x000000000304A510>
    
    m2 = re.search('foo', 'seafood') #可以匹配在字符中间的模式
    print m2   #<_sre.SRE_Match object at 0x00000000030016B0>
     

    匹配对象 和 group(), groups() 方法

    1、在处理正则表达式时,除 regex 对象外,还有另一种对象类型 - 匹配对象。这些对象是在 match()或 search()被成功调用之后所返回的结果。

    匹配对象有两个主要方法:group() 和 groups()
    2、group()方法或者返回所有匹配对象或是根据要求返回某个特定子组

    a. group()返回re整体匹配的字符串,
    b. group (n,m) 返回组号为n,m所匹配的字符串,如果组号不存在,则返回indexError异常


    3、groups()则很简单,它返回一个包含唯一或所有子组的元组。如果正则表达式中没有子组的话, groups() 将返回一个空元组,而 group()仍会返回全部匹配对象

    例子1:

    #!/usr/bin/env python
    #coding:utf-8
    
    import re
    a = "123abc456"
    print re.search("([0-9]*)([a-z]*)([0-9]*)",a).group()    #123abc456
    print re.search("([0-9]*)([a-z]*)([0-9]*)", a).group(0)  #123abc456
    print re.search("([0-9]*)([a-z]*)([0-9]*)", a).group(1)  #123
    print re.search("([0-9]*)([a-z]*)([0-9]*)", a).group(2)  #abc
    print re.search("([0-9]*)([a-z]*)([0-9]*)", a).groups()  #('123', 'abc', '456')

    究其因:

    1. 正则表达式中的三组括号把匹配结果分成三组

    m.group() == m.group(0) == 所有匹配的字符(即匹配正则表达式整体结果)
    group(1) 列出第一个括号匹配部分,group(2) 列出第二个括号匹配部分,group(3) 列出第三个括号匹配部分。
    m.groups() 返回所有括号匹配的字符,以tuple格式。m.groups() == (m.group(0), m.group(1), ...)

    例子2:

    #!/usr/bin/env python
    #coding:utf-8
    
    import re
    origin = "hello alex bcd alex lge alex acd 19"
    #无分组
    r = re.search('aw+',origin) #w:匹配任何数字字母字符,和[A-Za-z0-9_];+:匹配前面出现的正则表达式1次或多次
    # 获取匹配到的所有结果
    print(r.group())  #alex
    # 获取模型中匹配到的分组结果;
    print(r.groups())  #()
    # 获取模型中匹配到的分组结果
    print(r.groupdict())  #{}
    
    
    # 有分组
    
    # 为何要有分组?提取匹配成功的指定内容(先匹配成功全部正则,再匹配成功的局部内容提取出来)
    #查找a开头并且后面匹配任何字符,接着单个字符匹配任意次,最后以数字结尾
    r = re.search("a(w+).*(?P<name>d)$", origin)# 
    # 获取匹配到的所有结果
    print(r.group())    #alex bcd alex lge alex acd 19
    # 获取模型中匹配到的分组结果
    print(r.groups())     #('lex', '9')
    # 获取模型中匹配到的分组中所有执行了key的
    print(r.groupdict()) #{'name': '9'}

    用 findall()找到每个出现的匹配部分

    1、它用于非重叠地查找某字符串中一个正则表达式模式出现的情况
    2、findall()和 search()相似之处在于二者都执行字符串搜索,但 findall()和 match()与search()不同之处是,findall()总返回一个列表。如果 findall()没有找到匹配的部分,会返回空列表;如果成功找到匹配部分,则返回所有匹配部分的列表(按从左到右出现的顺序排列)

    #!/usr/bin/env python
    #coding:utf-8
    
    import re
    
    m = re.search('foo', 'seafood is food')
    #search只匹配模式的第一次出现的字串
    print m.group()  #foo
    
    m = re.findall('foo', 'seafood is food') #可以匹配全部匹配的出现
    print m  #['foo', 'foo']

     一些注意点

    1、re.match与re.search与re.findall的区别:

    re.match只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回None;而re.search匹配整个字符串,直到找到一个匹配。

    #!/usr/bin/env python
    #coding:utf-8
    
    import re
    a=re.search('[d]',"abc33").group()
    print(a)  #3
    p=re.match('[d]',"abc33")
    print(p)  #None
    b=re.findall('[d]',"abc33")
    print(b) #['3', '3']

    2、贪婪匹配与非贪婪匹配

    *?,+?,??,{m,n}?    前面的*,+,?等都是贪婪匹配,也就是尽可能匹配,后面加?号使其变成惰性匹配

    表达式 .* 的意思很好理解,就是单个字符匹配任意次,即贪婪匹配。
    表达式 .*? 是满足条件的情况只匹配一次,即懒惰匹配

    例子1:

    #!/usr/bin/env python
    #coding:utf-8
    
    import re
    a = re.findall(r"a(d+?)",'a23b')
    print(a)  #['2']
    b = re.findall(r"a(d+)",'a23b')
    print(b)  #['23']

    例子2:

    #!/usr/bin/env python
    #coding:utf-8
    import re
    a = re.match('<(.*)>','<H1>title<H1>').group()
    print(a)  #<H1>title<H1>
    b = re.match('<(.*?)>','<H1>title<H1>').group()
    print(b)  #<H1>

    例子3:

    #!/usr/bin/env python
    #coding:utf-8
    import re
    a = re.findall(r"a(d+)b",'a3333b')
    print(a)  #['3333']
    b = re.findall(r"a(d+?)b",'a3333b')
    print(b)  #['3333']
    
    #######################
    #这里需要注意的是如果前后均有限定条件的时候,就不存在什么贪婪模式了,非匹配模式失效。

     

    finditer函数

    和findall()函数有相同的功能,但返回的不是列表而是迭代器;

    搜索string,返回一个顺序访问每一个匹配结果(Match对象)的迭代器。找到 RE 匹配的所有子串,并把它们作为一个迭代器返回。
    对于每个匹配,该迭代器返回一个匹配对象

    #!/usr/bin/env python
    #coding:utf-8
    import re
    m = re.finditer('foo', 'seafood is food')
    for item in m:
        print item.group()
    
    #######
    
    '''
    foo
    foo
    '''

    用 sub()和 subn()进行搜索和替换

    sub

    sub方法提供一个替换值,可以是字符串或函数,和一个要被处理的字符串。sub使用re替换string中每一个匹配的子串后返回替换后的字符串。

    语法:

    sub(pattern, repl, string, count=0, flags=0)

    选项解释:

    使用repl替换string中每一个匹配的子串后返回替换后的字符串。
    
    当repl是一个字符串时,可以使用id或g、g引用分组,但不能使用编号0。
    
    当repl是一个方法时,这个方法应当只接受一个参数(Match对象),并返回一个字符串用于替换(返回的字符串中不能再引用分组)。
    
    count用于指定最多替换次数,不指定时全部替换。

    例子1:

    #!/usr/bin/env python
    #coding:utf-8
    import re
    m = re.sub('X', 'Mr. Smith', 'attn: X
    Dear X')
    print m
    """
    attn: Mr. Smith
    Dear Mr. Smith
    """

    例子2:

    import re
    text = "JGood is a handsome boy, he is cool, clever, and so on..."
    print(re.sub(r's+', '-', text))
    执行结果如下:
    JGood-is-a-handsome-boy,-he-is-cool,-clever,-and-so-on...
    
    其中第二个函数是替换后的字符串;本例中为'-'
    
    第四个参数指替换个数。默认为0,表示每个匹配项都替换。

    re.sub还允许使用函数对匹配项的替换进行复杂的处理。

    如:re.sub(r's', lambda m: '[' + m.group(0) + ']', text, 0);将字符串中的空格' '替换为'[ ]'。

    import re
    text = "JGood is a handsome boy, he is cool, clever, and so on..."
    print(re.sub(r's+', lambda m:'['+m.group(0)+']', text,0))
    执行结果如下:
    JGood[ ]is[ ]a[ ]handsome[ ]boy,[ ]he[ ]is[ ]cool,[ ]clever,[ ]and[ ]so[ ]on...

    subn

    subn()和 sub()一样,但它还返回一个表示替换次数的数字,替换后的字符串和表示替换次数的数字作为一个元组的元素返回

    3、语法:


     subn(pattern, repl, string, count=0, flags=0)

    选项解释:

    使用repl替换string中每一个匹配的子串后返回替换后的字符串。
    
    当repl是一个字符串时,可以使用id或g、g引用分组,但不能使用编号0。
    
    当repl是一个方法时,这个方法应当只接受一个参数(Match对象),并返回一个字符串用于替换(返回的字符串中不能再引用分组)。
    
    count用于指定最多替换次数,不指定时全部替换。

    用 split()分割(分隔模式)


    1、re 模块和正则表达式对象的方法 split()与字符串的 split()方法相似, 前者是根据正则表达式模式分隔字符串,后者是根据固定的字符串分割
    2、如果你不想在每个模式匹配的地方都分割字符串,你可以通过设定一个值参数(非零)来指定分割的最大次数
    3、如果分隔符没有使用由特殊符号表示的正则表达式来匹配多个模式,那 re.split()和string.split()的执行过程是一样的

    根据正则表达式中的分隔符把字符分割为一个列表,并返回成功匹配的列表

    格式:

    split(pattern, string, maxsplit=0, flags=0)

    maxsplit用于指定最大分割次数,不指定将全部分割。


    字符串也有类似的方法,但是正则表达式更加灵活

    #!/usr/bin/env python
    #coding:utf-8
    import re #使用 . 和 - 作为字符串的分隔符
    
    mylist = re.split('.|-', 'hello-world.data')
    print mylist   #['hello', 'world', 'data']

    例子:

    #!/usr/bin/env python
    #coding:utf-8
    
    
    import  re
    
    origin = "hello alex bcd alex lge alex acd 19"
    
    n1 = re.split("aw+",origin)
    print(n1)  #['hello ', ' bcd ', ' lge ', ' ', ' 19']
    
    n2 = re.split("aw+",origin,1)
    print(n2)  #['hello ', ' bcd alex lge alex acd 19']
    
    n3 = re.split("a(w+)",origin,1)
    print n3  #['hello ', 'lex', ' bcd alex lge alex acd 19']
    
    n4 = re.split("(aw+)",origin,1)
    print n4 #['hello ', 'alex', ' bcd alex lge alex acd 19']

     用flags时遇到的小坑

    print(re.split('a','1A1a2A3',re.I))#输出结果并未能区分大小写
    这是因为re.split(pattern,string,maxsplit,flags)默认是四个参数,当我们传入的三个参数的时候,系统会默认re.I是第三个参数,所以就没起作用。
    如果想让这里的re.I起作用,写成flags=re.I即可。

    正则表达式之split以及计算器思路

    #1 - 2 * ( (60-30 +(-40.0/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/14 )) - (-4*3)/ (16-3*2) )
    origin =" 1 - 2 * ( (60-30 +(-40.0/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/14 )) - (-4*3)/ (16-3*2) )"
    n = re.split("(([^()]+))",origin,1)
    print(n)

    正则表达式之计算器去括号实例

    方法1:

    方法1:
    def f1(ex):
        return 1
    
    #1 - 2 * ( (60-30 +(-40.0/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/14 )) - (-4*3)/ (16-3*2) )
    origin =" 1 - 2 * ( (60-30 +(-40.0/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/14 )) - (-4*3)/ (16-3*2) )"
    
    
    
    while True:
        print(origin)
        result = re.split("(([^()]+))",origin,1)
        if len(result) == 3:
            before = result[0]
            content = result[1]
            after = result[2]
            r = f1(content)
            new_str = before + str(r) + after
            origin = new_str
        else:
            final = f1(1+4)
            print(final)
            break

    方法2:

    def f1(ex):
        return 1
    
    #1 - 2 * ( (60-30 +(-40.0/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/14 )) - (-4*3)/ (16-3*2) )
    origin =" 1 - 2 * ( (60-30 +(-40.0/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/14 )) - (-4*3)/ (16-3*2) )"
    
    
    
    while True:
        print(origin)
        result = re.split("(([^()]+))",origin,1)
        if len(result) == 3:
            before,content,after = result
            r = f1(content)
            new_str = before + str(r) + after
            origin = new_str
        else:
            final = f1(1+4)
            print(final)
            break

    原始字符串

    >>> mm = "c:\a\b\c"
    >>> mm
    'c:\a\b\c'
    >>> print(mm)
    c:ac
    >>> print(mm)
    c:ac
    >>> re.match("c:\\",mm).group()
    'c:\'
    >>> ret = re.match("c:\\",mm).group()
    >>> print(ret)
    c:
    >>> ret = re.match("c:\\a",mm).group()
    >>> print(ret)
    c:a
    >>> ret = re.match(r"c:\a",mm).group()
    >>> print(ret)
    c:a
    >>> ret = re.match(r"c:a",mm).group()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: 'NoneType' object has no attribute 'group'
    >>>

    Python中字符串前面加上 r 表示原生字符串

    与大多数编程语言相同,正则表达式里使用""作为转义字符,这就可能造成反斜杠困扰。假如你需要匹配文本中的字符"",那么使用编程语言表示的正则表达式里将需要4个反斜杠"\":前两个和后两个分别用于在编程语言里转义成反斜杠,转换成两个反斜杠后再在正则表达式里转义成一个反斜杠。

    Python里的原生字符串很好地解决了这个问题,有了原始字符串,你再也不用担心是不是漏写了反斜杠,写出来的表达式也更直观。

    >>> ret = re.match(r"c:\a",mm).group()
    >>> print(ret)
    c:a

    表示数量

    实例1:

    需求:匹配出,一个字符串第一个字母为大小字符,后面都是小写字母并且这些小写字母可有可无

    #!/usr/bin/env python
    #coding:utf-8
    
    
    import  re
    
    ret = re.match("[A-Z][a-z]*","Mm")
    print ret.group()  #Mm
    
    ret = re.match("[A-Z][a-z]*","Aabcdef")
    print ret.group()  #Aabcdef

    实例2:需求:匹配出,变量名是否有效

    #!/usr/bin/env python
    #coding:utf-8
    
    import  re
    
    ret = re.match("[a-zA-Z_]+[w_]*","name1")
    print ret.group()  #name1
    
    ret = re.match("[a-zA-Z_]+[w+]*","_name")
    print ret.group() # _name
    
    ret = re.match("[a-zA-Z_]+[w_]*","2_name")
    print ret.group()  #匹配不到,AttributeError: 'NoneType' object has no attribute 'group'

    实例3:

    #!/usr/bin/env python
    #coding:utf-8
    
    import  re
    
    ret = re.match("[1-9]?[0-9]","123456")
    print ret.group()  #12
    
    ret = re.match("[1-9]?[0-9]","123456")
    print ret.group()  #12
    
    ret = re.match("[1-9]?[0-9]","123456")
    print ret.group()  #12

    实例4:需求:匹配出,8到20位的密码,可以是大小写英文字母、数字、下划线

    #!/usr/bin/env python
    #coding:utf-8
    
    
    import  re
    
    
    ret = re.match("[a-zA-Z0-9_]{6}","12a3g45678")
    print ret.group()  #12a3g4
    
    ret = re.match("[a-zA-Z0-9_]{8,12}","1ad12f23s34455ff66")
    print ret.group()  #1ad12f23s344

    表示边界

    示例1:$

    需求:匹配163.com的邮箱地址

    #!/usr/bin/env python
    #coding:utf-8
    
    import re
    # 正确的地址
    ret = re.match("[w]{4,20}@163.com", "xiaoWang@163.com")
    print ret.group()  #xiaoWang@163.com
    
    # 不正确的地址
    ret = re.match("[w]{4,20}@163.com", "xiaoWang@163.comheihei")
    print ret.group()  #xiaoWang@163.com
    
    # 通过$来确定末尾
    ret = re.match("[w]{4,20}@163.com$", "xiaoWang@163.comheihei")
    print ret.group() #报错,AttributeError: 'NoneType' object has no attribute 'group'

    示例2: 

    >>> re.match(r".*ver", "ho ver abc").group()
    'ho ver'
    
    >>> re.match(r".*ver", "ho verabc").group()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: 'NoneType' object has no attribute 'group'
    
    >>> re.match(r".*ver", "hover abc").group()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: 'NoneType' object has no attribute 'group'
    >>>

    示例3:B

    >>> re.match(r".*BverB", "hoverabc").group()
    'hover'
    
    >>> re.match(r".*BverB", "ho verabc").group()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: 'NoneType' object has no attribute 'group'
    
    >>> re.match(r".*BverB", "hover abc").group()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: 'NoneType' object has no attribute 'group'
    
    >>> re.match(r".*BverB", "ho ver abc").group()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: 'NoneType' object has no attribute 'group'

    匹配分组

    重新复习分组语法:

    字符    功能
    |    匹配左右任意一个表达式
    (ab)    将括号中字符作为一个分组
    
    um    引用分组num匹配到的字符串
    (?P<name>)    分组起别名
    (?P=name)    引用别名为name分组匹配到的字符串

     

    分组就是用一对圆括号()括起来的正则表达式,匹配出的内容就表示一个分组。从正则表达式的左边开始看,看到的第一个左括号(表示第一个分组,第二个表示第二个分组,依次类推,需要注意的是,有一个隐含的全局分组(就是0),就是整个正则表达式。

    分完组以后,要想获得某个分组的内容,直接使用group(num)和groups()函数去直接提取就行。

    命名分组

    命名分组就是给具有默认分组编号的组另外再给一个别名。命名分组的语法格式如下:

    (?P<name>正则表达式)    #name是一个合法的标识符

    (?P=name)通过命名分组名进行引用

    (?P=name) 字符P必须是大写的P,name表示命名分组的分组名

    (?P<name>)(?P=name) 引用分组的值匹配值必须与第一个分组匹配值相等才能匹配到

    注意:

    (?P<name>...)
    和普通的圆括号类似,但是子串匹配到的内容将可以用命名的name参数来提取。组的name必须是有效的python标识符,而且在本表达式内不重名。命名了的组和普通组一样,也用数字来提取,也就是说名字只是个额外的属性

    例如:

    1)    引用前一个分组,前后值相同都是2,故能匹配到

    例如:

    import re
    print re.match(r'(?P<xst>d)(?P=xst)','22').groups()  #('2',)
    print re.match(r'(?P<xst>d)(?P=xst)','22').group()  #22 

    2)    引用前一个分组,前后值不相同分别为2和3,故不能匹配到

     例如

    import re
    print re.match(r'(?P<xst>d)(?P=xst)','23').group() #AttributeError: 'NoneType' object has no attribute 'group'

    如:提取字符串中的ip地址

    例子1:

    >>> s = "ip='230.192.168.78',version='1.0.0'"
    >>> res =re.search(r"ip='(?P<ip>d+.d+.d+.d+).*", s)
    >>> res.group('ip')#通过命名分组引用分组
    '230.192.168.78'

    例子2:

    #!/usr/bin/env python
    #coding:utf-8
    
    import re
    s = "ip='230.192.168.78',version='1.0.0'"
    res = re.search("(?P<ip>d+.d+.d+.d+)", s)
    #通过命名分组引用分组
    print res.group('ip')  #230.192.168.78

     后向引用

     正则表达式中,放在圆括号()中的表示是一个组。然后你可以对整个组使用一些正则操作,例如重复操作符。
    要注意的是,只有圆括号()才能用于形成组。""用于定义字符集。{}用于定义重复操作。
    当用()定义了一个正则表达式组后,正则引擎则会把被匹配的组按照顺序编号,存入缓存。这样我们想在后面对已经匹配过的内容进行引用时,就可以用"数字"的方式或者是通过命名分组进行"(?P=name)"进行引用。1表示引用第一个分组,2引用第二个分组,以此类推, 引用第n个组。而则引用整个被匹配的正则表达式本身。这些引用都必须是在正则表达式中才有效,用于匹配一些重复的字符串。

    如:

    #通过命名分组进行后向引用
    >>> re.search(r'(?P<name>go)s+(?P=name)s+(?P=name)', 'go go go').group('name')
    'go'
    #通过默认分组编号进行后向引用
    >>> re.search(r'(go)s+1s+1', 'go go go').group()
    'go go go'

    交换字符串的位置

    >>> s = 'abc.xyz'
    >>> re.sub(r'(.*).(.*)', r'2.1', s)
    'xyz.abc'

    前向肯定断言、后向肯定断言

    前向肯定断言的语法:

    (?=pattern)

     (?<=pattern) 前向肯定断言表示你希望匹配的字符串前面是pattern匹配的内容时,才匹配。

    后向肯定断言的语法:

    (?<=pattern)

     (?=pattern) 后向肯定断言表示你希望匹配的字符串的后面是pattern匹配的内容时,才匹配

    前后向断言同时使用

    需要注意的是,如果在匹配的过程中,需要同时用到前向肯定断言和后向肯定断言,那么必须将后向肯定断言写在正则语句的前面,前向肯定断言写在正则语句的后面,表示后向肯定模式之后,前行肯定模式之前

    如:获取c语言代码中的注释内容

    >>> s1='''char *a="hello world"; char b='c'; /* this is comment */ int c=1; /* this is multiline comment */'''
    >>> re.findall( r'(?<=/*).+?(?=*/)' , s1 ,re.M|re.S)
    [' this is comment ', ' this is multiline comment ']

    (?<=/*)这个是后向肯定断言,表示'/*'之后。(?=*/)这个为前向肯定断言,表示'*/'之前,这两合并起来就是一个区间了,所以后向肯定断言放在前向肯定断言前面。

    前向否定断言、后向否定断言

    前向否定断言语法:

    (?!pattern)

     (?<!pattern) 前向否定断言表示你希望匹配的字符串的前面不是pattern匹配的内容时,才匹配.

    后向否定断言语法:

    (?<!pattern)

     (?!pattern) 后向否定断言表示你希望匹配的字符串后面不是pattern匹配的内容时,才匹配。

    前向否定和后向否定实例:

    #提取不是.txt结尾的文件
    >>> f1 = 'aaa.txt'
    >>> re.findall(r'.*..*$(?<!txt$)',f1)
    []
    #提取不以数字开头的文件
    >>> re.findall(r'^(?!d+).*','1txt.txt')
    []
    #提取不以数字开头不以py结尾的文件
    >>> re.findall(r'^(?!d+).+?..*$(?<!py$)','test.py')
    []
    >>> re.findall(r'^(?!d+).+?..*$(?<!py$)','test.txt')
    ['test.txt']

    注意:

    前向肯定(否定)断言括号中的正则表达式必须是能确定长度的正则表达式,比如w{3},而不能写成 w*或者w+或者w?等这种不能确定个数的正则模式符。

    示例1:

    需求:匹配出0-100之间的数字

    #coding=utf-8
    
    import re
    
    ret = re.match("[1-9]?d","8")
    print ret.group()
    
    ret = re.match("[1-9]?d","78")
    print ret.group()
    
    # 不正确的情况
    ret = re.match("[1-9]?d","08")
    print ret.group()
    
    # 修正之后的
    ret = re.match("[1-9]?d$","08")
    print.group() #这个是错误
    
    # 添加|
    ret = re.match("[1-9]?d$|100","8")
    ret.group()
    
    ret = re.match("[1-9]?d$|100","78")
    ret.group()
    
    ret = re.match("[1-9]?d$|100","08")
    ret.group()
    
    ret = re.match("[1-9]?d$|100","100")
    ret.group()

    示例2:( )

    需求:匹配出163、126、qq邮箱之间的数字

    #coding=utf-8
    
    import re
    
    ret = re.match("w{4,20}@163.com", "test@163.com")
    ret.group()
    
    ret = re.match("w{4,20}@(163|126|qq).com", "test@126.com")
    ret.group()
    
    ret = re.match("w{4,20}@(163|126|qq).com", "test@qq.com")
    ret.group()
    
    ret = re.match("w{4,20}@(163|126|qq).com", "test@gmail.com")
    ret.group()

    示例3:

    需求:匹配出<html>hh</html>

    #coding=utf-8
    
    import re
    
    # 能够完成对正确的字符串的匹配
    ret = re.match("<[a-zA-Z]*>w*</[a-zA-Z]*>", "<html>hh</html>")
    ret.group()
    
    # 如果遇到非正常的html格式字符串,匹配出错
    ret = re.match("<[a-zA-Z]*>w*</[a-zA-Z]*>", "<html>hh</htmlbalabala>")
    ret.group()
    
    # 正确的理解思路:如果在第一对<>中是什么,按理说在后面的那对<>中就应该是什么
    
    # 通过引用分组中匹配到的数据即可,但是要注意是元字符串,即类似 r""这种格式
    ret = re.match(r"<([a-zA-Z]*)>w*</1>", "<html>hh</html>")
    ret.group()
    
    # 因为2对<>中的数据不一致,所以没有匹配出来
    ret = re.match(r"<([a-zA-Z]*)>w*</1>", "<html>hh</htmlbalabala>")
    ret.group()

    示例4: umber

    需求:匹配出<html><h1>www.itcast.cn</h1></html>

    #coding=utf-8
    
    import re
    
    ret = re.match(r"<(w*)><(w*)>.*</2></1>", "<html><h1>www.itcast.cn</h1></html>")
    ret.group()
    
    ret = re.match(r"<(w*)><(w*)>.*</2></1>", "<html><h1>www.itcast.cn</h2></html>")
    ret.group()

    示例5:(?P<name>) (?P=name)

    需求:匹配出<html><h1>www.itcast.cn</h1></html>

    #coding=utf-8
    
    import re
    
    ret = re.match(r"<(?P<name1>w*)><(?P<name2>w*)>.*</(?P=name2)></(?P=name1)>", "<html><h1>www.itcast.cn</h1></html>")
    ret.group()
    
    ret = re.match(r"<(?P<name1>w*)><(?P<name2>w*)>.*</(?P=name2)></(?P=name1)>", "<html><h1>www.itcast.cn</h2></html>")
    ret.group()

    注意:(?P<name>)(?P=name)中的字母p大写

    分析 apache 访问日志

    写用于分析 apache 日志的脚本,主要要求如下:
    1、统计每个客户端访问 apache 服务器的次数
    2、将统计信息通过字典的方式显示出来
    3、分别统计客户端是 Firefox 和 MSIE 的访问次数
    4、分别使用函数式编程和面向对象编程的方式实现

    方案
    涉及到文本处理时,正则表达式将是一个非常强大的工具。匹配客户端的 IP 地址,可以使用正则表达式的元字符,匹配字符串可以直接使用字符的表面含义。
    入门级程序员的写法,使用顺序的结构,直接编写。这种方法虽然可以得出结果,但是代码难以重用。参考步骤一。
    进阶的写法可以采用函数式编程,方便日后再次使用。参考步骤二。
    最后,还可以使用 OOP 的编程方法,先定义一个统计类,该类将正则表达式作为它的数据属性。再定义一个方法,从指定的文件中搜索正则表达式出现的次数,并将其存入到一个字典中。参考步骤三

    步骤
    实现此案例需要按照如下步骤进行。
    步骤一:简单实现

    #!/usr/bin/env python
    #coding:utf-8
    
    import re
    logfile = '/var/log/httpd/access_log'
    cDict = {}
    patt_ip = '^d+.d+.d+.d+' #定义匹配 IP 地址的正则表达式
    
    with open(logfile) as f:
        for eachLine in f:
            m = re.search(patt_ip, eachLine)
            if m is not None:
                ipaddr = m.group()
            #如果 IP 地址已在字典中,将其值加 1,否则初始值设置为 1
                cDict[ipaddr] = cDict.get(ipaddr, 0) + 1
    print cDict

     注意:

    dict.get(key, default=None):对字典dict中的键key,返回它对应的值value,如果字典中不存在此键,则返回default的值 

     

    执行结果:需要客户端访问

    root@host-192-168-3-6 test]# python countweb.py 
    {'61.140.232.156': 11}

    步骤二:使用函数式编程实现

    #!/usr/bin/env python
    #coding:utf-8
    
    import re
    
    def countPatt(patt, fname): #定义可以在指定文件中搜索指定字符串的函数
        cDict = {}
        with open(fname) as f:
            for eachLine in f:
                m = re.search(patt, eachLine)
                if m is not None:
                    k = m.group()
                    cDict[k] = cDict.get(k, 0) + 1
        return cDict
    
    
    def test():
        logfile = '/var/log/httpd/access_log'
        patt_ip = '^d+.d+.d+.d+'
        print countPatt(patt_ip, logfile)
        patt_br = 'Firefox|MSIE'
        print countPatt(patt_br, logfile)
        
    if __name__ == '__main__':
        test()

    执行结果:

    [root@host-192-168-3-6 test]# python countweb.py 
    {'61.140.232.156': 11}
    {'Firefox': 11}

    步骤三:使用 OOP 方式实现

    #!/usr/bin/env python
    #coding:utf-8
    
    import re
    
    class MyCount(object): #定义类,将正则表达式作为其数据属性
        def __init__(self, patt):
            self.patt = patt
    
        def countPatt(self, fname): #定义统计正则表达式的方法
            cDict = {}
            with open(fname) as f:
                for eachLine in f:
                    m = re.search(self.patt, eachLine)
                    if m is not None:
                        k = m.group()
                        cDict[k] = cDict.get(k, 0) + 1
            return cDict
    
    def test():
        patt_ip = 'd+.d+.d+.d+'
        logfile = '/var/log/httpd/access_log'
        testip = MyCount(patt_ip)
        print testip.countPatt(logfile)
        patt_br = 'Firefox|MSIE'
        testbr = MyCount(patt_br)
        print testbr.countPatt(logfile)
        
    if __name__ == '__main__':
        test()

    执行结果:

    [root@host-192-168-3-6 test]# python countweb.py 
    {'61.140.232.156': 11}
    {'Firefox': 11}

     方法1:

    step1:

    >>> adict= {}
    >>> print adict.get('a',0)
    0
    >>> adict['a'] = adict.get('a',0)
    >>> adict
    {'a': 0}
    >>> adict['b'] = adict.get('b',0) + 1
    >>> adict
    {'a': 0, 'b': 1}
    >>> adict['b'] = adict.get('b',0) + 1
    >>> adict
    {'a': 0, 'b': 2}
    >>> 


    [root@host-192-168-3-6 ~]# yum -y install httpd
    [root@host-192-168-3-6 ~]# systemctl start httpd
    [root@host-192-168-3-6 ~]# systemctl status httpd
    [root@host-192-168-3-6 ~]# tail -f /var/log/httpd/access_log




    count_patt1.py内容:

    #!/usr/bin/env python
    #coding:utf8
    
    import re
    
    def count_patt(fname,patt):
        patt_dict = {}
        cpatt = re.compile(patt)
        with open(fname) as fobj:
            for line in fobj:
                m = cpatt.search(line)
                if m:
                    key = m.group()
                    #如果key不在字典中,设置值为1,否则加1
                    patt_dict[key] = patt_dict.get(key,0) + 1
                    '''
                    if key not in patt_dict:
                        patt_dict[key] = 1
                    else:
                        patt_dict[key] += 1
        '''
        return patt_dict
                        
    if __name__ == "__main__":
        log_file = "/var/log/httpd/access_log"
        ip_patt = "^(d+.){3}d+"
        br_patt = "Firefox|MSIE|Chrome|QQBrowser"
        print count_patt(log_file, ip_patt)
        print count_patt(log_file, br_patt)

    执行结果:

    [root@host-192-168-3-6 ~]# python count_patt.py 
    {'113.67.74.155': 250}
    {'Chrome': 95, 'Firefox': 24, 'QQBrowser': 22}

    count_patt2.py内容:

    #!/usr/bin/env python
    #coding:utf8
    
    import re
    
    def count_patt(fname,patt):
        patt_dict = {}
        cpatt = re.compile(patt)
        with open(fname) as fobj:
            for line in fobj:
                m = cpatt.search(line)
                if m:
                    key = m.group()
                    #如果key不在字典中,设置值为1,否则加1
                    patt_dict[key] = patt_dict.get(key,0) + 1
                    '''
                    if key not in patt_dict:
                        patt_dict[key] = 1
                    else:
                        patt_dict[key] += 1
        '''
        return patt_dict
                        
    def sort(adict):
        alist = []
        patt_list = adict.items()
        for i in range(len(patt_list)):
            greater = patt_list[0]
            for j in range(len(patt_list[1:])):
                greater = greater if greater[1] >=patt_list[j+1][1] else patt_list[j +1]
            alist.append(greater)
            patt_list.remove(greater)
            
        return alist
        
    
    if __name__ == "__main__":
        log_file = "/var/log/httpd/access_log"
        ip_patt = "^(d+.){3}d+"
        br_patt = "Firefox|MSIE|Chrome|QQBrowser"
        
        print count_patt(log_file, br_patt)
        ip_count = count_patt(log_file, ip_patt)
        print ip_count
        print sort(ip_count)

    执行结果:

    [root@host-192-168-3-6 ~]# python count_patt.py 
    {'113.67.74.155': 264}
    {'Chrome': 100, 'Firefox': 33, 'QQBrowser': 22}
    [root@host-192-168-3-6 ~]# python count_patt.py 
    {'Chrome': 100, 'Firefox': 33, 'QQBrowser': 22}
    {'113.67.74.155': 268}
    [('113.67.74.155', 268)]

    count_patt3.py内容:

    #!/usr/bin/env python
    #coding:utf8
    
    import re 
    import collections
    
    class CountPatt(object):
        def __init__(self,patt):
            self.cpatt = re.compile(patt)
            
        def count_patt(self,fname):
            c = collections.Counter()
            with open(fname) as fobj:
                for line in fobj:
                    m = self.cpatt.search(line)
                    if m:
                        c.update([m.group()])
                        
            return c
        
    
    if __name__ == "__main__":
        log_file = "/var/log/httpd/access_log"
        ip_patt = "^(d+.){3}d+"
        br_patt = "Firefox|MSIE|Chrome|QQBrowser"
        
        c1 = CountPatt(ip_patt)
        print c1.count_patt(log_file)
        print c1.count_patt(log_file).most_common(2)
        c2 = CountPatt(br_patt)
        print c2.count_patt(log_file)
        print c2.count_patt(log_file).most_common(2)

    执行结果:

    [root@host-192-168-3-6 ~]# python count_patt2.py 
    Counter({'113.67.74.155': 311, '47.88.191.116': 1, '139.162.88.63': 1})
    [('113.67.74.155', 311), ('47.88.191.116', 1)]
    Counter({'Chrome': 130, 'Firefox': 39, 'QQBrowser': 26})
    [('Chrome', 130), ('Firefox', 39)]
    [root@host-192-168-3-6 ~]# 

    参考:

    https://www.cnblogs.com/dodoye/p/6218192.html

     http://www.jb51.net/article/117035.htm

     https://www.cnblogs.com/larry-luo/p/10814919.html

    https://www.cnblogs.com/panwenbin-logs/p/5584864.html

  • 相关阅读:
    Eclipse导入Spring Boot项目后pom.xml出现红叉的解决办法
    ubuntu18.04中将刚下载解压的eclipse添加到启动器
    easyui datagrid设置一开始不加载数据
    Spring Boot开发八字箴言(以我过去这段时间的经验总结得到)
    Spring Boot中mybatis insert 如何获得自增id
    jquery控制一个元素是否显示
    easyui-datagrid配置宽度高度自适应
    html页面js响应回车
    Node.js ORM框架Sequelize使用示例
    Java遍历日期代码
  • 原文地址:https://www.cnblogs.com/zhongguiyao/p/11049384.html
Copyright © 2020-2023  润新知