Loading

《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预编译为模式对象,效率更高,
注意,datepatdatepat_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中的每一项,idx1开始
同时,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()接受listtupple等,也接受生成器表达式,比如,

>>> data = ['ACME', 50, 91.1]
>>> ','.join(str(d) for d in data)

2.15 字符串中插入变量

formatformat_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格
posted @ 2021-08-17 08:15  wwcg2235  阅读(65)  评论(0)    收藏  举报