2. python字符串的奇淫巧技
- 字符串分割
默认我们会使用Split方法进行字符串的分割,这种方式必须要传入一个单一的分隔符进行分割,如果希望更加灵活的分割字符串,最好使用re.split()方法,其支持传入多个正则模式,只要符合如何一个模式就会返回,返回的类型跟split一致
比如
>>> line = ‘asdf fjdk; afed, fjek,asdf, foo’
>>> import re
>>> re.split(r'[;,\s]\s*’, line)
上面我们就在其中指定了可以匹配,逗号,分号 乃至是空格
如果是括号捕获分组,那么匹配的文本也会在结果队列
r'(;|,|\s)\s*’
- 字符串开头匹配
如果是简单的进行开头结尾匹配,那么就是使用str.startwith() 或者 str.endwith()
>>> filename = ‘spam.txt’
>>> filename.endswith(‘.txt’)
同时支持输入一个元组作为参数进行匹配,比如
name.startswith((‘http:’, ‘https:’, ‘ftp:’))
这就需要注意,如果是一个list或者set类型的选择项,需要先使用tuple将其转换为元组类型
当然,我们也可以使用re.match函数进行相关的匹配,不过这样有些大材小用了
>>> re.match(‘http:|https:|ftp:’, url)
- 使用Shell通配符匹配
如果想要使用shell的*通配符匹配,则可以使用fnmatch进行实现
>>> fnmatch(‘foo.txt’, ‘*.txt’)
>>> fnmatch(‘foo.txt’, ‘?oo.txt’)
需要注意的是,这种方式不同操作系统的大小写敏感规则是不一样的
比如mac或者linux上,是大小写敏感的,window上则不一样
如果需要强匹配,则可以使用fnmatchcase()代替,完全使用模式大小写匹配
>>> fnmatchcase(‘foo.txt’, ‘*.TXT’)
- 字符串的匹配
字符串匹配只需要调用基本字符串就可以了,比如str.find() endswith() startswith()
当然,更加复杂的匹配需要使用正则表达式和re模块
比如我们的日期字符串为
>>> text1 = ’11/27/2012′
这时候我们可以利用re.match进行匹配
>>> if re.match(r’\d+/\d+/\d+’, text1):
如果希望多次利用一个模式进行匹配,这时候则建议进行编译
>>> datepat = re.compile(r’\d+/\d+/\d+’)
需要注意match是进行匹配,而如果想要获取符合模式的字符串,则更加应该使用findall函数
除了findall,还有着finditer(),返回一个迭代方式
如果希望忽略大小写进行匹配,则可以在操作的时候传入re.IGNORECASE标志
- 字符串的替换
如果是进行字符串的替换的话,简单可以使用str.replace函数
如果是复杂的模式,则是需要使用re.sub函数
可以进行多匹配,其需要多个参数,第一个参数是匹配模式,第二个参数是替换模式
比如
>>> datepat = re.compile(r'(\d+)/(\d+)/(\d+)’)
>>> datepat.sub(r’\3-\1-\2′, text)
我们进行了分组匹配,然后在替换模式中\3是前面的捕获组号
>>> text = ‘Today is 11/27/2012. PyCon starts 3/13/2013.’
‘Today is 2012-11-27. PyCon starts 2013-3-13.’
如果需要进行多次替换,则可以考虑先编译从而提升性能
还支持传入回调函数来进行替换
>>> def change_date(m):
… mon_name = month_abbr[int(m.group(1))] … return ‘{} {} {}’.format(m.group(2), mon_name, m.group(3)) … >>> datepat.sub(change_date, text) ‘Today is 27 Nov 2012. PyCon starts 13 Mar 2013.’ |
回调函数中的m
是其中匹配的一组,方便我们进行替换
最后还有一个re.subn函数,可以查看多少替换发生了
同样,python的正则表达式支持贪婪和非贪婪匹配
就是简单的正则匹配上加上 ?修饰符
这样就是要求这个匹配是非贪婪的
- Unicode文本标准化
由于Unicode中,某些字符可以使用多个编码来进行表示
>>> s1 = ‘Spicy Jalape\u00f1o’
>>> s2 = ‘Spicy Jalapen\u0303o’
>>> s1
‘Spicy Jalapeño’
>>> s2
‘Spicy Jalapeño’
>>> s1 == s2
False
所以我们需要进行文本标准化,这一部分我们使用的unicodedata模块下的normalize函数
比如我们上面展示的s1,s2,可以进行转换
>>> import unicodedata
>>> t1 = unicodedata.normalize(‘NFC’, s1) >>> t2 = unicodedata.normalize(‘NFC’, s2) >>> t1 == t2 True |
第一个参数表示了标准化的方式,支持NFC NFKD NFKC通过不同的标准化方式来使用不同的兼容特性
这里顺便提一嘴,虽然python支持在正则中使用unicode字符,但是由于支持的并不太好,所以最好使用一些第三方库进行支持
- 字符串的删除
简单的删除可以使用replace,替换特定字符串为空字符即可,或者是使用sub函数也可以
除此之外,我们可以使用strip来删除开头或者结尾的字符串
其中支持传入多个字符串
>>> s = ‘ hello world \n’
>>> s.strip()
‘hello world’
>>> t = ‘—–hello=====’
>>> t.strip(‘-=’)
‘hello’
除了这个之外,还有lstrip()函数和rstrip()函数
如果希望更多的替换方式,还可以使用translate()方法
对于这个translate方法,支持传入一个字典,进行使用
创建一个小的转换表格,进行使用translate方法
>>> s = ‘pýtĥöñ\fis\tawesome\r\n’
>>> remap = {
… ord(‘\t’) : ‘ ‘,
… ord(‘\f’) : ‘ ‘,
… ord(‘\r’) : None # Deleted
… }
>>> a = s.translate(remap)
>>> a
通过这样一个映射表,完成了相关的映射,从而将空白字符进行了删除
- 字符串格式化
对于字符串的格式化操作,如果只是需要对齐,直接使用ljust() rjust() center()
对应的是左对齐,右对齐以及居中对齐
>>> text = ‘Hello World’
>>> text.ljust(20)
‘Hello World ‘
>>> text.center(20)
‘ Hello World ‘
或者使用format函数,从而对字符串
比如
>>> format(text, ‘>20’)
‘ Hello World’
同样希望左对齐则是<,
居中对齐则是 ^
还支持指定一个对齐字符串,只需要在上面的特殊字符前面加 填充字符即可
>>> format(text, ‘=>20s’)
‘=========Hello World’
>>> format(text, ‘*^20s’)
‘****Hello World*****’
还支持格式化多个值
>>> ‘{:>10s} {:>10s}’.format(‘Hello’, ‘World’)
‘ Hello World’
那么就这个format函数,相比较老版本的 % 运算符,功能更为强大
- 字符串的合并
我们希望将多个字符串合并为一个大的字符串
此时我们最好能够将多个字符串放在一个序列或者iterable中
然后使用字符串的join方法
>>> parts = [‘Is’, ‘Chicago’, ‘Not’, ‘Chicago?’]
>>> ‘ ‘.join(parts)
Is Chicage Not Chicago?
当然,直接使用+ 也可以
>>> a = ‘Is Chicago’
>>> b = ‘Not Chicago?’
>>> a + ‘ ‘ + b
但是这样性能并不如join那么好
最后考虑下,如果想要编写构建大量小字符串的输出代码,可以考虑使用yield语句产生输出片段
def sample():
yield ‘Is’
yield ‘Chicago’
yield ‘Not’
yield ‘Chicago?’
text = ”.join(sample())
- format函数
对于一些需要在字符串中声明变量,并在后续进行替换的问题的需求,可以考虑使用fotmat函数进行解决,比如
>>> s = ‘{name} has {n} messages.’
>>> s.format(name=’Guido’, n=37)
‘Guido has 37 messages.’
如果在当前能够访问的变量域中有对应的变量值的话,那么可以直接使用format_map加vars()
>>> name = ‘Guido’
>>> n = 37
>>> s.format_map(vars())
‘Guido has 37 messages.’
或者我们使用vars() 将一个对象实例包起来使用
>>> class Info:
def __init__(self, name, n):
self.name = name
self.n = n
>>> a = Info(‘Guido’,37)
>>> s.format_map(vars(a))
但是在format运行过程中,如果存在着变量缺失的情况,那么会抛出KeyError
对于这种问题,可以考虑定义一个对象,继承字典
class safesub(dict):
“””防止key找不到”””
def __missing__(self, key):
return ‘{‘ + key + ‘}’
然后将原本的对象进行包装后,传递给format_map()
更进一步可以利用方法进行包装format
import sys
def sub(text): return text.format_map(safesub(sys._getframe(1).f_locals)) |
这里我们使用sys._getframe(1)来获取调用者的栈帧,并且访问f_locals获取其中的局部变量,这种情况并不推荐,但是还是声明一下,这样的操作是访问到了一个复制本地变量的字典,并不会影响原本的变量,所以还是具有安全性保证的。
- 指定输出列宽
使用textwrap模块来格式化字符串的输出,比如有如下的长字符串
s = “Look into my eyes, look into my eyes, the eyes, the eyes, \
the eyes, not around the eyes, don’t look around the eyes, \
look into my eyes, you’re under.”
我们希望指定输出行宽,则可以如下
>>> print(textwrap.fill(s, 70))
Look into my eyes, look into my eyes, the eyes, the eyes, the eyes,
not around the eyes, don’t look around the eyes, look into my eyes,
you’re under.
>>> print(textwrap.fill(s, 40))
Look into my eyes, look into my eyes,
the eyes, the eyes, the eyes, not around
the eyes, don’t look around the eyes,
look into my eyes, you’re under.
>>> print(textwrap.fill(s, 40, initial_indent=’ ‘))
Look into my eyes, look into my
eyes, the eyes, the eyes, the eyes, not
around the eyes, don’t look around the
eyes, look into my eyes, you’re under.
>>> print(textwrap.fill(s, 40, subsequent_indent=’ ‘))
Look into my eyes, look into my eyes,
the eyes, the eyes, the eyes, not
around the eyes, don’t look around
the eyes, look into my eyes, you’re
under.
这一方法很适合于字符串打印,如果是希望输出的宽度自动匹配终端大小的时候,那么直接在textwrap.fill中填入os.get_terminal_size()方法即可
- 字节字符串和字符串
字节字符串支持很多和文本字符串一样的内置操作,比如
>>> data = b’Hello World’
>>> data[0:5]
b’Hello’
即使是正则表达式,也是支持匹配字节字符串的,但是需要注意,正则表达式本身也要是字符串
但是需要注意,直接读取字节字符串,会得到对应的字节值,比如
>>> b = b’Hello World’ # Byte string
>>> b[0]
72
但是字节字符串虽然速度很快,但是需要注意,字节字符串在很多的时候,会由于无法自动的编解码导致问题
所以仍然不建议使用