python每日读02-re模块与正则表达式

本文通过Python的re模块深入讲解正则表达式的应用,包括验证扑克牌字符串的合法性、查找对子、模拟scanf()函数、电话本数据处理、文本替换以及词法分析。通过实例展示了如何使用正则表达式进行字符串匹配、分割、替换和查找,以及在实际问题中的应用技巧。
摘要由CSDN通过智能技术生成

python每日读02-re模块与正则表达式

例程学习与注释

检查对子

在这个例子里,我们可以使用以下辅助函数来更好的显示匹配对象

def displaymatch(match):
    if match is None:
        return None
    return '<Match: %r, groups=%r>' % (match.group(), match.groups())

假设你在写一个扑克程序,一个玩家的一手牌为五个字符的串,每个字符表示一张牌,“a” 就是 A, “k” K, “q” Q, “j” J, “t” 为 10, “2” 到 “9” 表示2 到 9。
要看给定的字符串是否有效,我们可以按照以下步骤

>>> valid = re.compile(r"^[a2-9tjqk]{5}$")
>>> displaymatch(valid.match("akt5q"))  # Valid.
"<Match: 'akt5q', groups=()>"
>>> displaymatch(valid.match("akt5e"))  # Invalid.
>>> displaymatch(valid.match("akt"))    # Invalid.
>>> displaymatch(valid.match("727ak"))  # Valid.
"<Match: '727ak', groups=()>"

最后一手牌,“727ak” ,包含了一个对子,或者两张同样数值的牌。要用正则表达式匹配它,应该使用向后引用如下

>>> pair = re.compile(r".*(.).*\1")
>>> displaymatch(pair.match("717ak"))     # Pair of 7s.
"<Match: '717', groups=('7',)>"
>>> displaymatch(pair.match("718ak"))     # No pairs.
>>> displaymatch(pair.match("354aa"))     # Pair of aces.
"<Match: '354aa', groups=('a',)>"

要找到对子包含的是哪一张牌,应该按照下面的方式使用 group() 方法:

>>> pair.match("717ak").group(1)
'7'

# Error because re.match() returns None, which doesn't have a group() method:
>>> pair.match("718ak").group(1)
Traceback (most recent call last):
  File "<pyshell#23>", line 1, in <module>
    re.match(r".*(.).*\1", "718ak").group(1)
AttributeError: 'NoneType' object has no attribute 'group'

>>> pair.match("354aa").group(1)
'a'

模拟scanf()

下表提供scanf()格式符和正则表达式大致相同的映射

scanf()格式符正则表达式
%c.
%5c.{5}
%d[-+]?\d+
%e, %E, %f, %g[-+]?(\d+(\.\d*)?|\.\d+)([eE] [-+]?\d+)?
%i[-+]?(0[xX][\dA-Fa-f]+|0[0-7]*|\d+)
%o[-+]?[0-7]+
%s\S+
%u\d+
%x, %X[-+]?(0[xX])?[\dA-Fa-f]+

例子:从文件名和数字提取 字符串

/usr/sbin/sendmail - 0 errors, 4 warnings
#对以上一段文本进行匹配
#scanf()格式化格式如下
%s - %d errors, %d warnings
#等价正则表达式如下
(\s+) - (\d+) errors, (\d+) warnings
'''(\s+)	\s匹配所有的Unicode字符,‘+’表示对其前方的正则表达式匹配任意多次
同理(\d+)
'''

建立一个电话本

原文本如下:

>>> text = 
"""Ross McFluff: 834.345.1254 155 Elm Street
...
... Ronald Heathmore: 892.345.3428 436 Finley Avenue
... Frank Burger: 925.541.7625 662 South Dogwood Way
...
...
... Heather Albrecht: 548.326.4584 919 Park Place"""

split()可以将字符串拆分成列表,从而便于修改文本的结构,十分有用。

>>> entries = re.split("\n+", text)
>>>#先将文本拆分成为一行一行的字段,而后对每一行就行正则匹配
>>> entries#展示拆分后的效果
['Ross McFluff: 834.345.1254 155 Elm Street',
'Ronald Heathmore: 892.345.3428 436 Finley Avenue',
'Frank Burger: 925.541.7625 662 South Dogwood Way',
'Heather Albrecht: 548.326.4584 919 Park Place']
>>> [re.split(":? ", entry, 4) for entry in entries]
#正则表达式":? "指匹配一个或者0个:后接一个空格的类似格式,即为可以匹配冒号空格,与纯空格两种情况。整句意为:以匹配的到的匹配对象为分割的标志,将整个entry文本分割四次。
[['Ross', 'McFluff', '834.345.1254', '155', 'Elm Street'],
['Ronald', 'Heathmore', '892.345.3428', '436', 'Finley Avenue'],
['Frank', 'Burger', '925.541.7625', '662', 'South Dogwood Way'],
['Heather', 'Albrecht', '548.326.4584', '919', 'Park Place']]

同由此我们就将用户的名字、性氏、电话号码以及房间号分离出来了

文字整理

sub( )可以将字符中匹配到的样式替换为你自己想要的文字格式,以下整个例子就很哈的使用了sub( )。

>>> def repl(m):
...     inner_word = list(m.group(2))#生成中间文本列表
...     random.shuffle(inner_word)#将中间文本列表打乱
...     return m.group(1) + "".join(inner_word) + m.group(3)
#返回一个中间的单词被打乱的字符串
>>> text = "Professor Abdolmalek, please report your absences promptly."
>>> re.sub(r"(\w)(\w+)(\w)", repl, text)
'''匹配单词的首字母,第二个到倒数第二个字母,以及尾字母,生成三个匹配组。分别为‘P’‘refesso’‘r’。将中间的字符串,在此例子中为‘refesso’,传入repl()函数中打乱,将打乱得到的字符串替换原本的中间的字符串(‘refesso’)
'''
'Poefsrosr Aealmlobdk, pslaee reorpt your abnseces plmrptoy.'
>>> re.sub(r"(\w)(\w+)(\w)", repl, text)
'Pofsroser Aodlambelk, plasee reoprt yuor asnebces potlmrpy.'

整体操作

找到所有副词

findall( )将会匹配所有的能够同正则表达式匹配的字符串,而search( )只能够匹配第一个与正则表达式匹配的字符串,可以用作大批量文本的操作。

>>> text = "He was carefully disguised but captured quickly by police."
>>> re.findall(r"\w+ly", text)#这个则是匹配所有以ly结尾的字符串
['carefully', 'quickly']
找到所有副词和位置

finditer( )同findall( )的返回值不同,将会返回一个匹配对象,因此可以对匹配到的结果进行与匹配对象有关的相关操作。可以得到匹配对象在原文本中的位置等其它的详细信息

>>> text = "He was carefully disguised but captured quickly by police."
>>> for m in re.finditer(r"\w+ly", text):#同上匹配含义
...     print('%02d-%02d: %s' % (m.start(), m.end(), m.group(0)))
07-16: carefully
40-47: quickly

原始字符记法

(r"text") 保持正则表达式正常。否则,每个正则式里的反斜杠(‘’) 都必须前缀一个反斜杠来转义。比如,下面两行代码功能就是完全一致的

>>> re.match(r"\W(.)\1\W", " ff ")
<re.Match object; span=(0, 4), match=' ff '>
>>> re.match("\\W(.)\\1\\W", " ff ")
<re.Match object; span=(0, 4), match=' ff '>

当需要匹配一个字符反斜杠,它必须在正则表达式中转义。在原始字符串记法,就是 r"\"。否则就必须用 “\\”,来表示同样的意思

>>> re.match(r"\\", r"\\")
<re.Match object; span=(0, 1), match='\\'>
>>> re.match("\\\\", r"\\")
<re.Match object; span=(0, 1), match='\\'>

写一个词法分析器

这个例子的目的是写一个词法器或者词法分析器分析自负床,并且分类为目录组

import collections
import re

Token = collections.namedtuple('Token', ['type', 'value', 'line', 'column'])

def tokenize(code):
    keywords = {'IF', 'THEN', 'ENDIF', 'FOR', 'NEXT', 'GOSUB', 'RETURN'}
    token_specification = [
        ('NUMBER',   r'\d+(\.\d*)?'),  # 整数或十进制数 \d+ 匹配整数或小数的整数部分
                                       # (\.\d*)\.匹配.字符,后借0到任意多位的整数
                                       # ? 最后的?表示(\.\d*)这一部分匹不匹配都行                 	
        ('ASSIGN',   r':='),           # Assignment operator
        							   ''' 仅仅匹配=运算符'''
        ('END',      r';'),            # Statement terminator
                                       '''仅匹配;'''
        ('ID',       r'[A-Za-z]+'),    # Identifiers
        							   '''[A-Za-z]匹配一位英文字符,不论大小写
        							   + 对[A-Za-z]进行1次到任意次的匹配'''
        ('OP',       r'[+\-*/]'),      # Arithmetic operators
                                       '''直接匹配+ -(这里用了\来转义)* / 四个符号
                                       '''
        ('NEWLINE',  r'\n'),           # Line endings
        							   '''直接匹配\n'''
        ('SKIP',     r'[ \t]+'),       # Skip over spaces and tabs
                                       '''[ \t] 匹配空格即" "或者\t
                                       + 对[A-Za-z]进行1次到任意次的匹配'''
        ('MISMATCH', r'.'),            # Any other character
                                       #'.'匹配除了换行符的任意字符
    ]
    tok_regex = '|'.join('(?P<%s>%s)' % pair for pair in token_specification)#将所有正则表达式用或逻辑连接
    #(?P<%s>%s) 是(?P<name>...)的匹配形式,其中token_specification的每个元组的左侧字符串为name,右侧的正则表达式为...
    line_num = 1
    line_start = 0
    for mo in re.finditer(tok_regex, code):
        kind = mo.lastgroup
        value = mo.group()
        column = mo.start() - line_start
        if kind == 'NUMBER':
            value = float(value) if '.' in value else int(value)
        elif kind == 'ID' and value in keywords:
            kind = value
        elif kind == 'NEWLINE':
            line_start = mo.end()
            line_num += 1
            continue
        elif kind == 'SKIP':
            continue
        elif kind == 'MISMATCH':
            raise RuntimeError(f'{value!r} unexpected on line {line_num}')
        yield Token(kind, value, line_num, column)

statements = '''
    IF quantity THEN
        total := total + price * quantity;
        tax := price * 0.05;
    ENDIF;
'''

for token in tokenize(statements):
    print(token)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值