《Python Cookbook v3.0.0》Chapter2 字符串、文本
感谢:
https://github.com/yidao620c/python3-cookbook
如有侵权,请联系我整改。
本文章节会严格按照原书(以便和原书对照,章节标题可能会略有修改),内容会有增删。
2.1 用界定符分割字符串
string.split()可用于简单的分割,
复杂的使用re,
import re
示例,
>>> line = 'asdf fjdk; afed, fjek,asdf, foo'
>>> re.split(r'[;,\s]\s*', line)
['asdf', 'fjdk', 'afed', 'fjek', 'asdf', 'foo']
注意,捕捉的时候,以下3种情况,
>>> re.split(r'([;,\s])\s*',line) //写法1
['asdf', ' ', 'fjdk', ';', 'afed', ',', 'fjek', ',', '|asdf', ',', 'foo']
>>> re.split(r'(;|,|\s)\s*',line) //写法2
['asdf', ' ', 'fjdk', ';', 'afed', ',', 'fjek', ',', '|asdf', ',', 'foo']
>>> re.split(r'(;,\s)\s*',line) //不匹配的情况,返回原序列
['asdf fjdk; afed, fjek,|asdf, foo']
捕获后,可以用
切片来分别获取值和分隔符,像这样,
>>> fields=re.split(r'(;|,|\s)\s*',line)
>>> fields[::2]
['asdf', 'fjdk', 'afed', 'fjek', '|asdf', 'foo']
>>> fields[1::2]
[' ', ';', ',', ',', ',']
然后可以像这样重组,注意,分隔符比值少一个,需要补一个,否则会丢一个数据,
>>> ''.join(v+d for v,d in zip(fields[::2],fields[1::2]))
'asdf fjdk;afed,fjek,|asdf,'
>>> ''.join(v+d for v,d in zip(fields[::2],fields[1::2]+['']))
'asdf fjdk;afed,fjek,|asdf,foo'
2.3 用Shell通配符匹配字符串
与操作系统耦合。暂不考虑去用它。
2.4 字符串匹配和搜索
text.startswith()以xxx开头,
text.endswith()以xxx结尾,
text.find()查找xxx的位置,返回下标,
startwith()、endswith()支持多个匹配,比如name.endswith(('.c', '.h')),
但是,它们必须传入tupple,所以如果不是tupple,需要先转换成tupple。
复杂匹配可以用re,
import re
示例,
>>> text
'today is 11/27/2012. pycon starts 3/13/2013.'
>>> datepat = re.compile(r'(\d+)/(\d+)/(\d+)')
>>> datepat
re.compile('(\\d+)/(\\d+)/(\\d+)')
>>> datepat_no_capture = re.compile(r'\d+/\d+/\d+')
>>> datepat_no_capture
re.compile('\\d+/\\d+/\\d+')
>>> datepat.findall(text)
[('11', '27', '2012'), ('3', '13', '2013')]
>>> datepat_no_capture.findall(text)
['11/27/2012', '3/13/2013']
re.compile预编译为模式对象,效率更高,
注意,datepat和datepat_no_capture的差异,前者带(),后者没有,
带()意味着捕获分组,结果以tupple形式返回,以便对每一项做后续处理
在正则表达式中,
r,表示原始字符串,否则反斜杠也需要转义,'(\\d+)/(\\d+)/(\\d+)'
除了findall(),match()可以用来从头(起始)匹配,尾部不管,
示例,
>>> datepat.match('11/27/2012')
<re.Match object; span=(0, 10), match='11/27/2012'>
>>> datepat.match('x11/27/2012')
>>> datepat.match('11/27/2012x')
<re.Match object; span=(0, 10), match='11/27/2012'>
>>> m=datepat.match('11/27/2012x')
>>> m.group(0) //同m[0]
'11/27/2012'
>>> m.group(1) //同m[1]
'11'
>>> m.group(3)
'2012'
>>> m.groups()
('11', '27', '2012')
注意,
m.group(0),0表示整个内容,group中的每一项,idx从1开始
同时,m.group(x)和m[x]是等价的
与
findall()类似的,还有finditer(),前者返回的是<tupple>,或者是<re.Match>,也就是上面的m,
示例,
>>> it=datepat.finditer(text)
>>> it
<callable_iterator object at 0x0000000003B9F5E0>
for item in it:
print(item[0])
11/27/2012
3/13/2013
for item in datepat.findall(text):
print(type(item))
<class 'tuple'>
for item in datepat.finditer(text):
print(type(item))
<class 're.Match'>
2.5 字符串的搜索和替换
简单替换可使用str.replace(),
复杂的,还是用re,
示例,
>>> datepat = re.compile(r'(\d+)/(\d+)/(\d+)')
>>> datepat.sub(r'\3-\1-\2', text)
'Today is 2012-11-27. PyCon starts 2013-3-13.'
不过这里要注意,
m[0]或者m.group(0)表示第0组,
上面的\3表示第三组,但是不能用\0! 否则,结果就是,
'Today is \x00. PyCon starts \x00.'
2.6 字符串的搜索和替换(忽略大小写)
re接口支持指定flags,针对大小写,是re.IGNORECASE,
示例,
>>> re.findall('python', text, flags=re.IGNORECASE)
>>> re.sub('python', 'snake', text, flags=re.IGNORECASE)
需要注意的是,上面的sub例子,不论text中是
PYTHON还是python,都会替换成小写的snake,
如果有保持原大小写不变替换的需求,可以给sub传入一个回调,回调的入参是match对象,比如,
def matchcase(word):
def replace(m):
text = m.group()
if text.isupper():
return word.upper()
elif text.islower():
return word.lower()
elif text[0].isupper():
return word.capitalize()
else:
return word
return replace
>>> re.sub('python', matchcase('snake'), text, flags=re.IGNORECASE)
'UPPER SNAKE, lower snake, Mixed Snake'
2.7 贪婪匹配和最短匹配
*,贪婪匹配,
?,最短匹配,
示例,
>>> str_pat=re.compile(r'\"(.*)\"')
>>> text2='aaaa"skldjsdlkfjs"---sksksks---"aabcdd"00000'
>>> str_pat.findall(text2)
['skldjsdlkfjs"---sksksks---"aabcdd']
>>> str_pat2=re.compile(r'\"(.*?)\"')
>>> str_pat2.findall(text2)
['skldjsdlkfjs', 'aabcdd']
2.8 多行匹配
如果字符串是跨行的,比如,
>>> text2 = '''/* this is a
... multiline comment */
... '''
这时,用上文的匹配是匹配不到的,需要如下,
>>> comment = re.compile(r'/\*((?:.|\n)*?)\*/')
[TODO] 仍没有搞清楚
?:的含义,看着像三元表达式,但是写法又不是三元表达式的写法
注意,.|\n的原因是,.无法匹配换行符,
不过,re.compile()接受re.DOTALL,如字面意思,dot all,点可匹配任意字符,
>>> comment = re.compile(r'/\*((?:.|\n)*?)\*/')
```python
>>> comment = re.compile(r'/\*(.*?)\*/', re.DOTALL)
2.9 去除多余字符
str.strip()
默认会去除首尾,空格、换行,
可以带参,比如,
>>> s=' lksajkfaslkjf slslsl aaa \n\n\n '
>>> s.strip()
'lksajkfaslkjf slslsl aaa'
>>> s2=' lksajkfaslkjf slslsl aaa \n\n\n ccc '
>>> s2.strip()
'lksajkfaslkjf slslsl aaa \n\n\n ccc'
>>> s3=' ----=== lksajkfaslkjf slslsl aaa \n\n\n ccc ======---'
>>> s3.strip(' -=')
'lksajkfaslkjf slslsl aaa \n\n\n ccc'
如果要去除中间的,需要用替换,比如str.replace(),re.sub(),注意,两者都不会改变原字符串,
示例,
>>> s.replace(' ','')
'lksajkfaslkjfslslslaaa\n\n\n'
>>> s
' lksajkfaslkjf slslsl aaa \n\n\n '
>>> re.sub('\s+',' ',s)
' lksajkfaslkjf slslsl aaa '
其中,
\s在正则中表示空白字符,包括,空格、制表、换行
文中,举了一个文件操作的例子,使用生成器,
with open(filename) as f:
lines = (line.strip() for line in f)
for line in lines:
print(line)
(line.strip() for line in f)创建的是一个生成器,并不会预读所有数据,省空间,且高效
2.13 字符串对齐
string提供了
ljust(),rjust(),center(),默认用空格对齐,可带参,用某个字符对齐,
但推荐更通用的format,format不单可格式化字符串,也可以格式化数字,
示例,
>>> s='abc'
>>> format(s,'>7')
' abc'
>>> format(s,'^7')
' abc '
>>> format(s,'*^7')
'**abc**'
>>> format(s,'*^8') //注意,中间对齐,会往前靠,右边多个*
'**abc***'
>>> format(s,'*<8')
'abc*****'
>>> x=123.4234631
>>> format(x,'^10.2f')
' 123.42 '
如果需要格式化多个值,可以这样,
>>> '{}{}'.format('aa','bbb')
'aabbb'
>>> '{:>10}{:>10}'.format('aa','bbb')
' aa bbb'
[TODO] 不清楚这里的
:是什么含义,不加,则报错
2.14 拼接字符串
简单的拼接可以用+,比如,a+b,
但是+会拷贝一个对象,还有垃圾回收,尽量不用或少用,
可以用join(),
示例,
>>> parts = ['Is', 'Chicago', 'Not', 'Chicago?']
>>> ' '.join(parts)
'Is Chicago Not Chicago?'
>>> ','.join(parts)
'Is,Chicago,Not,Chicago?'
join()接受list、tupple等,也接受生成器表达式,比如,
>>> data = ['ACME', 50, 91.1]
>>> ','.join(str(d) for d in data)
2.15 字符串中插入变量
用format和format_map,
示例,
>>> s='{}{}---{}'
s
'{}{}---{}'
>>> s.format(1,2,3)
'12---3'
>>> s='{a}{b}---{c}'
>>> s.format(c=1,b=2,a=3)
'32---1'
>>> a=9
>>> b=8
>>> c=7
>>> s.format_map(vars())
'98---7'
vars()也接收class,
class info:
def __init__(self,a,b,c):
self.a=a
self.b=b
self.c=c
>>> ci=info(123,312,222)
>>> s.format_map(vars(ci))
'123312---222'
2.16 以指定列宽格式化字符串
import textwrap
>>> print(textwrap.fill(s, 70)) //列宽70
>>> print(textwrap.fill(s, 40)) //列宽40
>>> print(textwrap.fill(s, 40, initial_indent=' ')) //首行,空2格
>>> print(textwrap.fill(s, 40, subsequent_indent=' ')) //除首行外,空2格

浙公网安备 33010602011771号