《Python 编程》笔记(十七)

来源:互联网 发布:男生不追女生 知乎 编辑:程序博客网 时间:2024/05/23 20:34

引言

本节笔记记录的是在 Python 中处理文本的一些方法。本节重点是学习如何在 Python 中使用正则表达式。

Python 内置字符串方法

常用字符串方法

以下是一些常用的字符串方法,更多的方法使用 dir(str) 查看。如今在 Python 中,所有常用的字符串方法都可以用于 strbytes 字符串。

  • str.find(substr):查找子字符串;
  • str.replace(old, new):替换子字符串;
  • str.split(delimiter):分割字符串;
  • str.join(iterable):用分隔符连接字符串;
  • str.strip():删除两端空白字符;
  • str.lstrip()str.rstrip():分别删除左边和右边的空白字符;
  • str.rjust(width):宽度固定的区域内将字符串向右对齐(使用 format 也可以控制);
  • str.upper():转为大写字母;
  • str.isupper()
  • str.isdigit()
  • str.endswith(sub-or-tuple):测试是否以某个子串或者多个子串中的某个结尾;
  • str.startswith(sub-or-tuple)

简单实践

  • 简单的字符串分割和连接操作:

    >>> str1 = 'spam foo bar temp Good'>>> str1.split(' ')['spam', 'foo', 'bar', 'temp', 'Good']>>> ', '.join(str1.split(' '))'spam, foo, bar, temp, Good'
  • 计算文件中数据的和(完整的代码文件参见 ch19/str_tools):

    • 首先使用 gen_dummy_data 函数生成需要计算的数据文件,得到的文件如下图所示:

    • 然后使用 cal_sum 函数计算文件中所有行数据的和以及所有列数据的和。这里使用的重要函数是 str.split('\t') 分割得到列数据,再进行转换后即可计算。以下为核心代码:

    with open(filename, 'r') as infile:    # 按行快速读取,然后进行计算    for row in infile:        # 数据使用 tab 分割        data = [int(x.strip()) for x in row.split('\t') if x.strip() != '']        # 计算行和        rows_sum.append(sum(data))        # 累积列和        if len(cols_sum):            for i, x in enumerate(data):                cols_sum[i] += x        else:            cols_sum = data

注意

  • 尽量使用字符串对象方法来避免如正则表达式这样的方法,使用内置字符串方法性能会更好。

正则表达式

  • 简要地说,正则表达式只是字符串,它们定义了在其它字符串中进行匹配的模式。给定一个字符串和匹配模式,如果匹配成功,将会得到相应的回答,并可以挑选出匹配的子串。

  • 正则表达式可以写得非常复杂,它们可以代替手动编写大段的文本处理代码,解放双手!

  • Python 中,正则表达式是由 re 模块提供支持的。re 模块包含了很多函数,可以执行匹配等操作。re 同时支持 bytesstr 字符串,但二者不可混用。

  • 推荐书籍:Mastering Regular Expressions,掌握高级的正则表达式技巧。

  • 两种方式开始匹配:

    • 直接调用顶级方法开始执行匹配;
    • 预先编译好模式对象,后期多次调用时效率会更高。
  • 示例:

    >>> email_a = 'hello@foo.com'# `.` 表示任意字符,`*` 表示重复0次或更多# `()` 表示要把匹配到的子串保留下来>>> pattern = '.*@(.*).com'>>> obj = re.match(pattern, email_a)# 获取到保留的子串>>> obj.groups()('foo',)# 下面的正则表达式解释为:# 忽略字符串前面的0个或多个空格或 tab,并且`Hello` 和 `hello` 都可以匹配,同时忽略`hello`后面的# 1个以上的空格或 tab,然后获取中间的字符 `(.*)`,同样地,`world` 中的 `w` 或 `W` 都可以匹配。>>> patt = '[ \t]*[Hh]ello[ \t]+(.*)[Ww]orld'>>> line = '    Hello    fooworld'>>> obj = re.match(patt, line)>>> obj<_sre.SRE_Match object; span=(0, 18), match='\tHello    fooworld'>>>> obj.groups()('foo',)# 应用在字节字符串上>>> patt = b'[ \t]*[Hh]ello[ \t]+(.*)[Ww]orld'>>> line = b' hello    barWorld'>>> obj = re.match(patt, line)>>> obj<_sre.SRE_Match object; span=(0, 18), match=b' hello    barWorld'>>>> obj.group(0)b' hello    barWorld'>>> obj.group(1)b'bar'
  • 文本分割:事实上,有些情况下,内置字符串方法无法完成一些分割或替换的操作,如 aaa---bbb===ccc,如果想要分割后的结果为 aaa, bbb, ccc,使用 str.split() 可能需要多次才能做到,但正则表达式则会更灵活!

  • 正则表示进行分割、替换和匹配等操作示例:

    >>> import re# 简单的字符串分割>>> re.split('---', 'aaa---bbb---ccc')['aaa', 'bbb', 'ccc']# 简单的字符串替换>>> re.sub('---', '...', 'aaa---bbb---ccc')'aaa...bbb...ccc'# 多分隔符>>> text = 'foo---bar===spam'>>> re.split('---|===', text)['foo', 'bar', 'spam']>>> re.sub('---|===', '***', text)'foo***bar***spam'# 多种单个字符>>> text = 'foo-bar=spam'>>> re.split('[-=]', text)['foo', 'bar', 'spam']>>> re.sub('[=-]', '&', text)'foo&bar&spam'# 包括组合>>> re.split('(--)|(==)', 'foo--bar==spam')['foo', '--', None, 'bar', None, '==', 'spam']# 显示匹配的部分,但不显示组合>>> re.split('(?:--)|(?:==)', 'foo--bar==spam')['foo', 'bar', 'spam']# 匹配示例>>> re.match('(.*)/(.*)/(.*)', 'path/to/home').groups()('path', 'to', 'home')>>> re.match('<(.*)>/<(.*)>/<(.*)>', '<path>/<to>/<home>').groups()('path', 'to', 'home')# 忽略前面0到多个泛空格符号>>> re.match('\s*<(.*)>/<(.*)>/<(.*)>', '   <path>/<to>/<home>').groups()('path', 'to', 'home')# 一个复杂点的匹配>>> re.match('\s*(path)\s*([a-zA-Z]*)\s+(.*?)\s*!\s*', ' pathTo home ! ').groups()('path', 'To', 'home')
  • re.findall 方法可以在文本中找到所有匹配的子串。re.search 方法与 re.findall 类似,不过它在首次匹配成功后停止。下面来看看一些使用的示例:

    # 找到所有的匹配组合并返回>>> re.findall('<(.*?)>', '<html><head></head><body></body></html>')['html', 'head', '/head', 'body', '/body', '/html']>>> re.findall('<(.*?)>', ' <html> <head></head>/ <body>    </body></html> ')['html', 'head', '/head', 'body', '/body', '/html']>>> re.findall('<(.*?)>/?<(.*?)>', '<path>/<to>/<my><home>')[('path', 'to'), ('my', 'home')]# 返回第一个匹配的结果,然后停止匹配>>> re.search('<(.*?)>/?<(.*?)>', '<path>/<to>/<my><home>').groups()('path', 'to')# `.` 匹配除了行为符以外的任何字符,使用 `(?s)` 来匹配多行文本>>> re.findall('<(.*?)>.*<(.*?)>', '<path> \n <to>\n<home>')[]# 贪婪匹配>>> re.findall('(?s)<(.*?)>.*<(.*?)>', '<path> \n <to>\n<home>')[('path', 'home')]# 非贪婪匹配>>> re.findall('(?s)<(.*?)>.*?<(.*?)>', '<path> \n <to>\n<home>')[('path', 'to')]
  • 为了能够方便记忆,可以使用 (?P<name>) 的模式将匹配的子串组合与特定的名称关联,并在匹配后的通过名称提取它们。

    >>> re.search('(?P<part1>\w*)/(?P<part2>\w*)', 'xxxx/abc/dddd}').groups()('xxxx', 'abc')>>> re.search('(?P<part1>\w*)/(?P<part2>\w*)', 'xxxx/abc/dddd}').groupdict(){'part2': 'abc', 'part1': 'xxxx'}# 根据键名提取,更加容易记忆>>> re.search('(?P<part1>\w*)/(?P<part2>\w*)', 'xxxx/abc/dddd}').group('part1')'xxxx'# 这样就可以使用字典的键来访问每个匹配项!参见下方的参考。书上的例子给的不好,该用下面这个说明用法。>>> for x in re.finditer('(?P<part1>\w*)/(?P<part2>\w*)', 'xyz/abc  ddd/fff'):            x.groupdict(){'part2': 'abc', 'part1': 'xyz'}{'part2': 'fff', 'part1': 'ddd'}
  • [^ ]\- 用法示例:

    # 在范围更广的界定符两侧做分割>>> re.split(' +', text)'foo', 'bar', 'bala']# 另外一种:提取非分隔符的字符串>>> re.findall('[^ ]+', text)['foo', 'bar', 'bala']# 更复杂点的例子>>> re.findall('[^ .\-^&\*]+', text)['xxx', 'yyy', 'b', 'jfld', 'e', 'e']

re 模块

  • 该模块有多种函数可以使用。同样,也可以编译好一个模式对象,再调用相关函数执行匹配。
  • 模块级别函数:

    • compile:编译生成正则表达式对象;
    • match:若字符串起始处有 0 到多个匹配模式的字符串,则返回相应的匹配对象,否则返回 None;
    • search
    • findall
    • finditer
    • split
    • sub
    • subn:和 sub 相同,它返回一个元组:(新字符串, 替换次数);
    • escape:返回一个所有非字母或数字字符都转义过的字符串,该字符串可以作为文字字符串来编译。
  • 此外,编译好的模式对象也有很多类似的方法,具体参见官方文档。

  • 匹配对象自身的属性:

    • group(g)
    • group(g1, g2, ...):返回与模式中单个或数个圆括号中的组合匹配的子串。接手组合名称。组合编号从 1 开始,组合 0 表示与模式匹配的整个字符串。传入多个组合编号时返回一个元组,如果省略则默认为 0;
    • groups():返回一个由所有组合的匹配子串组成的元组;
    • groupdict():返回一个字典,包含匹配对象中所有名称的组合;
    • start([group]), end([group]):返回与 group 相匹配的子串的起始与结束位置索引;
    • span([group]):返回一个带有两个元素的元组:(start([group]), end([group])
    • expand([template]):进行反斜杠组合替换。

正则表达式模式

  • 通常匹配得到最长的匹配字符串,除非使用了非贪婪匹配操作符。

正则表达式相关资源

  • Regular Expression HOWTO;
  • 维基百科:正则表达式;
  • 正则表达式在线测试;
  • 菜鸟教程:正则表达式教程。

参考

  • Stack Overflow: re.findall which returns a dict of named capturing groups?

结语

本篇笔记也是整个《Python 编程》系列的最后一篇。实际上,由 Mark Luttz 编著的《Python 编程》(Programming Python) 中文版分为上下两册,涉及到的内容也是非常多的。我只是挑选了其中比较感兴趣的部分进行了学习,并做了这些笔记。总的来说,我觉得还是从高手那儿学到不少知识,虽然有些凌乱,不过相信在今后的实践中不断使用和总结定会有所提高的。

0 0
原创粉丝点击