赞
踩
字符串对象的split()方法适用场景很少(不适合于多分隔符或者分隔符后空格数目未知的情况)。
import re
line = 'asdf dasdasdas; dasdasda, dadasd'
print(re.split(r'[;,\s]\s*', line))
['asdf', 'dasdasdas', 'dasdasda', 'dadasd']
import re
line = 'asdf dasdasdas; dasdasda, dadasd'
print(re.split(r'(;|,|\s)\s*', line))
['asdf', ' ', 'dasdasdas', ';', 'dasdasda', ',', 'dadasd']
我们发现,捕获可以获取()里面对应的模式,即分隔符
获取分隔符常常很有用,比如用分隔符重构字符串
import re
line = 'asdf dasdasdas; dasdasda, dadasd'
fields = re.split(r'(;|,|\s)\s*', line)
#::表示隔2取,默认以0开始,如0,2,4,...
values = fields[::2]
#以1开始隔2取
delimiters = fields[1::2] + ['']
print(values)
print(delimiters)
#''.join表示连接
print(''.join(v + d for v, d in zip(values, delimiters)))
['asdf', 'dasdasdas', 'dasdasda', 'dadasd']
[' ', ';', ',', '']
asdf dasdasdas;dasdasda,dadasd
如果不想获取分隔符,并且想用括号来对正则表达式分段,注意使用?:
import re
line = 'asdf dasdasdas; dasdasda, dadasd'
fields = re.split(r'(?:;|,|\s)\s*', line)
print(fields)
['asdf', 'dasdasdas', 'dasdasda', 'dadasd']
filename = 'spam.txt'
print(filename.endswith('txt'))
print(filename.startswith('file'))
print('http://www.python.org'.startswith('http:'))
True
False
True
filename.startswith(('txt', 'spam'))
需要注意的地方:
元组不能替换为列表或者集合,如果为列表或集合,注意先使用tuple()转换为元组
import os
if any(name.startswith(('.c', '.h')) for name in os.listdir(dirname)):
用于判断某个目录下是否具有特定类型的文件
from fnmatch import fnmatch
print(fnmatch('foo.txt', '*.txt'))
print(fnmatch('foo.txt', '?oo.txt'))
print(fnmatch('Dat45.csv', 'Dat[0-9]*'))
需要注意的是,fnmatch()的大小写判断依赖于操作系统。
from fnmatch import fnmatch,fnmatchcase
#无论任何操作系统,遵守约定俗成的大小写规则
print(fnmatchcase('foo.txt', '*.TXT'))
False
需要注意的地方:
fnmatch()是字符串方法和强大的正则表达式之间的折中,如果只需要简单的通配符机制,那么这是一个不错的选择。
如果你想匹配的文本是简单的字面值,那么可以使用startswith(),endswith(),find()来匹配
如果是更复杂的匹配,那么就要用到正则表达式了
import re
text1 = '3/25/2018'
text2 = 'March 25, 2018'
if re.match(r'\d+/\d+/\d+', text1):
print('yes')
else:
print('no')
if re.match(r'\d+/\d+/\d+', text2):
print('yes')
else:
print('no')
yes
no
如果需要使用同一模式来多次匹配字符串,那么需要先用compile编译正则表达式
import re
text1 = '03/25/2018'
datepat = re.compile(r'\d+/\d+/\d+')
if datepat.match(text1):
print('yes')
else:
print('no')
yes
需要注意的是,match总是匹配第一次出现的模式,如果想匹配所有模式,使用findall
import re
text = 'Today is 03/25/2018.Yesterday is 03/24/2018.'
datepat = re.compile(r'\d+/\d+/\d+')
print(datepat.findall(text))
['03/25/2018', '03/24/2018']
import re
datepat = re.compile(r'(\d+)/(\d+)/(\d+)')
m = datepat.match('03/25/2018')
print(m)
print(m.group(0), m.group(1), m.group(2), m.group(3), m.groups())
text = 'Today is 03/25/2018.Yesterday is 03/24/2018'
print(datepat.findall(text))
for month, day, year in datepat.findall(text):
print('{}-{}-{}'.format(month, day, year))
<_sre.SRE_Match object; span=(0, 10), match='03/25/2018'>
03/25/2018 03 25 2018 ('03', '25', '2018')
[('03', '25', '2018'), ('03', '24', '2018')]
03-25-2018
03-24-2018
注意到,使用捕获很利于后续处理,因为捕获将匹配的模式分开了
findall找出所有匹配并且返回列表,如果想迭代处理,那么可以使用finditer()
import re
datepat = re.compile(r'(\d+)/(\d+)/(\d+)')
text = 'Today is 03/25/2018.Yesterday is 03/24/2018'
for m in datepat.finditer(text):
print(m.groups())
('03', '25', '2018')
('03', '24', '2018')
对使用正则表达式的简单总结:
1.编译正则表达式,返回pattern对象
2.使用match(),find(),findall(),finditer()等方法进行匹配
对于match,需要注意,它只检查字符串的开始,这就导致有时候不是你想要的结果
import re
datepat = re.compile(r'(\d+)/(\d+)/(\d+)')
m = datepat.match('11/27/2012dzxdzxdzx')
print(m)
print(m.group())
<_sre.SRE_Match object; span=(0, 10), match='11/27/2012'>
11/27/2012
因此需要用到结束标志$
总结一下需要注意的地方:使用正则表达式之前记得使用compile()编译,可以减少开销
对于普通的字面字符串而言,使用str.replace()
text = 'how do you'
print(text.replace('do', 'are'))
how are you
对于更复杂的情况,使用re.sub().
import re
text = 'Today is 03/25/2018.Yesterday is 03/24/2018'
#\3表示取捕获组的第三个,即年
#re.sub()的第一个参数为匹配的模式,第二个参数是替换的模式
print(re.sub(r'(\d+)/(\d+)/(\d+)', r'\3-\1-\2', text))
Today is 2018-03-25.Yesterday is 2018-03-24
import re
text = 'Today is 03/25/2018.Yesterday is 03/24/2018'
datepat = re.compile(r'(\d+)/(\d+)/(\d+)')
print(datepat.sub(r'\3-\1-\2', text))
Today is 2018-03-25.Yesterday is 2018-03-24
对于更加复杂的替换,可以使用一个回调函数来定义替换部分
import re
from calendar import month_abbr
#回调函数的参数是一个match对象,通常由find或者match返回,可以调用group提取匹配特定部分
def change_date(m):
#m.group(1)提取了月的部分,并且由month_abbr()转换为了月份缩写形式
mon_name = month_abbr[int(m.group(1))]
#注意这里的{}相当于占位符,由format指定的参数来替换
return '{} {} {}'.format(m.group(2), mon_name, m.group(3))
text = 'Today is 03/25/2018.Yesterday is 03/24/2018'
datepat = re.compile(r'(\d+)/(\d+)/(\d+)')
print(datepat.sub(change_date, text))
Today is 25 Mar 2018.Yesterday is 24 Mar 2018
整个功能相当于,datepat调用sub(),通过编译的正则表达式获取到了匹配,即match对象,将match对象传递给回调函数,回调函数进行处理,返回的结果替换匹配,一次替换结束,再进行第二次替换,依次进行,直到结束。
如果想知道进行了多少次替换,可以使用re.subn()
import re
from calendar import month_abbr
text = 'Today is 03/25/2018.Yesterday is 03/24/2018'
datepat = re.compile(r'(\d+)/(\d+)/(\d+)')
newtext, n = datepat.subn(r'\3-\1-\2', text)
print(newtext)
print(n)
Today is 2018-03-25.Yesterday is 2018-03-24
2
import re
text = 'UPPER PYTHON,lower python, Mixed Python'
print(re.findall('python', text, flags = re.IGNORECASE))
print(re.sub('python', 'case', text, flags = re.IGNORECASE))
['PYTHON', 'python', 'Python']
UPPER case,lower case, Mixed case
这种做法暴露了一个缺陷。不能根据匹配内容的大小写替换对应的大小写内容(比如如果python为大写,仍然替换的是case)
import re
#这种写法的意思是,每次调用re.sub,会调用matchcase,而matchcase返回replace,
#于是将匹配对象match传递给了replace,replace在matchcase的作用范围内,于是可以
#获取word的值进行处理
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
text = 'UPPER PYTHON,lower python, Mixed Python'
print(re.sub('python', matchcase('case'), text, flags = re.IGNORECASE))
我们可以发现,替换的case随着python的大写,小写,首字母大写而变化
先来看一个例子
import re
str_pat = re.compile(r'\"(.*)\"')
text1 = 'Computer says "no."'
print(str_pat.findall(text1))
text2 = 'Computer says "no." Phone says "yes."'
print(str_pat.findall(text2))
['no.']
['no." Phone says "yes.']
注意到匹配了整个”no.” Phone says “yes.”“,而我们想要的应该是”no.”和”yes.”。这是由于正则表达式中的*为贪婪的(greedy),所以匹配总会找出符合条件的最长的匹配。为了解决这个问题,需要使用?
import re
str_pat = re.compile(r'\"(.*?)\"')
text2 = 'Computer says "no." Phone says "yes."'
print(str_pat.findall(text2))
['no.', 'yes.']
?操作符使匹配非贪心,并且使匹配最短
先来看一个处理注释的例子
import re
comment = re.compile(r'/\*(.*?)\*/')
text1 = '/* this is a comment */'
text2 = '''/* this is a
multiline comment */
'''
print(comment.findall(text1))
print(comment.findall(text2))
[' this is a comment ']
[]
我们会发现匹配多行的例子失败了,这是因为’.’虽然可以匹配任意字符,但是不能匹配换行符。
解决方法1
import re
#注意,这里使用了?:非捕获符
comment = re.compile(r'/\*((?:.|\n)*?)\*/')
text2 = '''/* this is a
multiline comment */
'''
print(comment.findall(text2))
[' this is a\n multiline comment ']
解决方法2
import re
comment = re.compile(r'/\*(.*?)\*/', re.DOTALL)
text2 = '''/* this is a
multiline comment */
'''
print(comment.findall(text2))
[' this is a\n multiline comment ']
需要注意的地方:
re.DOTALL适用于简单的情况,如果正则表达式相当复杂可能会出问题。因此首推编写出正确的正则表达式,即第一种方法。
当处理Unicode字符串时,你需要确保每个字符串拥有相同的潜在表示形式。
对于Unicode字符集,有些字符可以存在多种表示形式。看一个例子
s1 = 'Spicy Jalape\u00f1o'
s2 = 'Spicy Jalapen\u0303o'
print(s1)
print(s2)
print(s1 == s2)
print(len(s1))
print(len(s2))
Spicy Jalapeño
Spicy Jalapeño
False
14
15
“Spicy Jalapeño”存在两种表示形式,第一种是一个整体\u00f1,第二种是n结合一个结合字符(combining character)\u0303
为了解决这个问题,我们需要使用unicodedata模块将字符串转换为同一种形式
import unicodedata
s1 = 'Spicy Jalape\u00f1o'
s2 = 'Spicy Jalapen\u0303o'
#normalize()的第一个参数指定正规化形式
#NFC代表完全组成
#NFD代表完全解题,使用结合字符
t1 = unicodedata.normalize('NFC', s1)
t2 = unicodedata.normalize('NFC', s2)
print(t1 == t2)
print(ascii(t2))
t3 = unicodedata.normalize('NFD', s1)
t4 = unicodedata.normalize('NFD', s2)
print(t3 == t4)
print(ascii(t4))
True
'Spicy Jalape\xf1o'
True
'Spicy Jalapen\u0303o'
总结:
1.正规化是相当重要的,因为你不知道你的输入文本是什么形式,尤其你无法控制输入的时候
2.正规化也可以用来过滤文本,如下所示
import unicodedata
s1 = 'Spicy Jalape\u00f1o'
t1 = unicodedata.normalize('NFD', s1)
#unicodedata.combing()可以识别出结合字符
print(''.join(c for c in t1 if not unicodedata.combining(c)))
Spicy Jalapeno
注意到,结合字符被过滤了
正则表达式库可以初步识别一些基本的Unicode字符类型,如\d可以识别数字
import re
num = re.compile('\d+')
print(num.match('123'))
#如果你想匹配特定Unicode字符,可以使用转义字符
print(num.match('\u0661\u0662\u0663'))
<_sre.SRE_Match object; span=(0, 3), match='123'>
<_sre.SRE_Match object; span=(0, 3), match='١٢٣'>
看一个比较麻烦的例子
总结一下关于处理Unicode需要注意的地方:
一定要注意正规化,将所有字符串都处理为同一种表示形式
s = ' hello word \n'
print(s.strip())
print(s.lstrip())
print(s.rstrip())
t = '---------helloxxxxxxx'
print(t.lstrip('-'))
print(t.strip('-x'))
hello word
hello word
hello word
helloxxxxxxx
hello
需要注意的地方:
1,strip()默认去除空白符(包括换行符),但是也可以指定去除特定字符
2.strip()只处理左右两边的空白处,不处理中间的。如果想处理中间的空白字符,需要使用replace或者re.sub()
with open(filename) as f:
#注意这里使用的是生成器表达式,因此会返回一个迭代器,而不是创建临时的列表保存数据
lines = (line.strip() for line in f)
for line in lines:
pass
在一些很简单的层面上,可以使用str.upper(),str.lower()或者str.replace(),re.sub()等方法来处理文本,或者使用之前讲到的unicodedata.normalize()来正规化文本。但是更进一层,可以使用translate()
注意到,translate()方法做的相当于是文本处理的映射。
更进一步,清除所有结合字符
可以看出,先通过dict.fromkeys建立了一个以所有结合字符为键,值为None的字典,然后translate使用这个字典将结合字符映射为空,即清除掉
再看一个例子,将Unicode字符集中的数字字符替换为ASCII码的等价形式
总结:
1.越简单,通常越快。例如str.replace()通常是最快的,尽管可能需要调用多次。
2.translate()对于字符->字符转换,或者字符->清除也是很快的
3.没有通用的最好的方法。模式是尝试各种方法,找出最优性能的那一个。
text = 'Hello World'
#填充到20个字符,不足的地方填充空白,字符串左移
print(text.ljust(20))
##填充到20个字符,不足的地方填充空白,字符串右移
print(text.rjust(20))
##填充到20个字符,不足的地方填充空白,字符串往中间移动
print(text.center(20))
Hello World
Hello World
Hello World
#指定填充字符
print(text.rjust(20, '='))
print(text.center(20, '*'))
=========Hello World
****Hello World*****
text = 'Hello World'
print(format(text, '>20'))
print(format(text, '<20'))
print(format(text, '^20'))
print(format(text, '=>20s'))
print(format(text, '*^20s'))
Hello World
Hello World
Hello World
=========Hello World
****Hello World*****
#注意,format可以一次性格式化多个字符串
print('{:>10s} {:>10s}'.format('Hello', 'World'))
Hello World
对于format()需要注意的地方:
它不仅适用于字符串,它适用于所有值类型。因此format()比其他方法也更具一般性
x = 1.2345
print(format(x, '>10'))
print(format(x, '^10.2f'))
1.2345
1.23
text = 'Hello World'
print('%-20s' % text)
print('%20s' % text)
Hello World
Hello World
总结:
format()方法功能最强大,也更具一般性。另外,尽量不要用’%’
如果你想连接序列或者可迭代对象,最快的方法是join()
parts = ['Is', 'Chicago', 'Not', 'Chicago?']
print(' '.join(parts))
Is Chicago Not Chicago?
如果只是想连接很少的字符串,那么可以使用’+’
a = 'is'
b = 'you'
print(a + ' ' + b)
is you
‘+’可以是format的替换
如果只是连接字面值,可以这样做
a = 'Hello ' 'World'
print(a)
Hello World
需要注意的地方:
当连接大量字符串时,’+’号的开销很巨大,因为有内存拷贝和垃圾回收等开销(跟java一样,字符串多了用StringBuilder).这种情况一定要用join()
注意到,str()字符串转换和使用生成器表达式进行字符串连接同时进行。
有时候,字符串连接是不必要的。看看例子
如果涉及到I/O,那么字符串的连接需要一些权衡
对于版本1,当chunk1和chunk2很小时,会比版本2还快,因为版本2由于有两次I/O操作,会有额外开销,但是当chunk1和chunk2很大时,版本1的额外开销很大,因为它会产生临时结果以及内存拷贝等开销
def sample():
yield 'Is'
yield 'Chicago'
yield 'Not'
yield 'Chicago'
text = ''.join(sample())
for part in sample():
f.write(part)
#短短几行就实现了缓冲区的功能,是不是相当酷(我说的是Python)
def combine(source, maxsize):
parts = []
size = 0
for part in source:
parts.append(part)
size += len(part)
if size >= maxsize:
yield ''.join(parts)
parts = []
size = 0
yield ''.join(parts)
for part in combine(sample(), 32768):
f.write(part)
注意到,生成器函数并不在意数据组合的细节,他只负责产生数据(搬运工)
s = '{name} has {n} messages'
print(s.format(name = 'xxx', n = 'xxx'))
xxx has xxx messages
如果变量已经定义过了,那么可以结合使用format_map()和vars()
name = 'xxx'
n = 'xxx'
s = '{name} has {n} messages'
print(s.format_map(vars()))
xxx has xxx messages
vars()还有一个特性,那就是适用于实例对象
class Info:
def __init__(self, name, n):
self.name = name
self.n = n
s = '{name} has {n} messages'
a = Info('xxx', 11)
print(s.format_map(vars(a)))
xxx has 11 messages
需要注意的是,format()和format的一个缺陷是不能处理缺失值
s = '{name} has {n} messages'
print(s.format(name = 'xxx'))
Traceback (most recent call last):
File "C:\Users\Administrator\Desktop\test.py", line 3, in <module>
print(s.format(name = 'xxx'))
KeyError: 'n'
一个解决办法是定义一个字典子类,编写missing()方法
class safesub(dict):
def __missing__(self, key):
return '{' + key + '}'
name = 'xxx'
n = 'xxx'
del(name)
s = '{name} has {n} messages'
print(s.format_map(safesub(vars())))
{name} has xxx messages
注意到,缺失的数据没有被替换
如果你发现你经常使用format_map,那么应该用一个方法将它包装起来,这个方法用到了”frame hack”
import sys
class safesub(dict):
def __missing__(self, key):
return '{' + key + '}'
def sub(text):
return text.format_map(safesub(sys._getframe(1).f_locals))
name = 'xxx'
n = 111
print(sub('Hello {name}'))
print(sub('{n} hello'))
print(sub('your favourite color is {color}'))
Hello xxx
111 hello
your favourite color is {color}
需要注意的是,以上两种方式都没有format()好
总结:
1.format()的优势在于,可以结合字符串格式化(alignment, padding, numerical formatting)等使用
2.f_locals是一个本地变量的拷贝,类型为字典,因此不用担心改变它会产生额外影响
假设你有这样一个字符串,text = ‘foo = 23 +42 * 10’
为了符号化这个字符串,你需要某种识别模式的方式,例如转化为这样的形式
为了符号化,第一步就需要定义所有可能的符号,包括空格。方法是正则表达式和命名捕获组(named capture group)
我们注意到,一般形式为?p<符号名称>+正则表达式
符号化的第一步完成了,第二步是使用scanner()
总结:
1.必须想清楚每种可能的符号,如果有任何模式未被匹配,scanner的扫描过程就会终止,这也是为什么需要特别定义空字符的符号
2.符号的顺序也需要注意
BNF:
EBNF:
import re
import collections
NUM = r'(?P<NUM>\d+)'
PLUS = r'(?P<PLUS>\+)'
MINUS = r'(?P<MINUS>-)'
TIMES = r'(?P<TIMES>\*)'
DIVIDE = r'(?P<DIVIDE>/)'
LPAREN = r'(?P<LPAREN>\()'
RPAREN = r'(?P<RPAREN>\))'
WS = r'(?P<WS>\s+)'
master_pat = re.compile('|'.join([NUM, PLUS, MINUS, TIMES, DIVIDE, LPAREN, RPAREN, WS]))
Token = collections.namedtuple('Token', ['type', 'value'])
def generate_tokens(text):
scanner = master_pat.scanner(text)
for m in iter(scanner.match, None):
tok = Token(m.lastgroup, m.group())
if tok.type != 'WS':
yield tok
class ExpressionEvaluator:
def parse(self, text):
self.tokens = generate_tokens(text)
self.tok = None
self.nexttok = None
self.advance()
return self.expr()
def advance(self):
self.tok, self.nexttok = self.nexttok, next(self.tokens, None)
def _accept(self, toktype):
if self.nexttok and self.nexttok.type == toktype:
self.advance()
return True
else:
return False
def _expect(self, toktype):
if not self._accept(toktype):
raise SyntaxError('Expected ' + toktype)
def expr(self):
expval = self.term()
while self._accept('PLUS') or self._accept('MINUS'):
op = self.tok.type
right = self.term()
if op == 'PLUS':
expval += right
elif op == 'MINUS':
expval -= right
return expval
def term(self):
termval = self.factor()
while self._accept('TIMES') or self._accept('DIVIDE'):
op = self.tok.type
right = self.factor()
if op == 'TIMES':
termval *= right
elif op == 'DIVIDE':
termval /= right
return termval
def factor(self):
if self._accept('NUM'):
return int(self.tok.value)
elif self._accept('LPAREN'):
expval = self.expr()
self._expect('RPAREN')
return expval
else:
raise SyntaxError('Expected or LPAREN')
e = ExpressionEvaluator()
print(e.parse('2'))
print(e.parse(' 2 + (3 * 3) + 4 / 5 + (5 / 5) * 10'))
2
21.8
class ExpressionTreeBuilder(ExpressionEvaluator):
def expr(self):
expval = self.term()
while self._accept('PLUS') or self._accept('MINUS'):
op = self.tok.type
right = self.term()
if op == 'PLUS':
expval = ('+', expval, right)
elif op == 'MINUS':
expval = ('-', expval, right)
return expval
def term(self):
termval = self.factor()
while self._accept('TIMES') or self._accept('DIVIDE'):
op = self.tok.type
right = self.factor()
if op == 'TIMES':
termval = ('*', termval, right)
elif op == 'DIVIDE':
termval = ('/', termval, right)
return termval
def factor(self):
if self._accept('NUM'):
return int(self.tok.value)
elif self._accept('LPAREN'):
expval = self.expr()
self._expect('RPAREN')
return expval
else:
raise SyntaxError('Expected or LPAREN')
e = ExpressionTreeBuilder()
print(e.parse('2 + 3 * (4 + 5 / 6)'))
('+', 2, ('*', 3, ('+', 4, ('/', 5, 6))))
解析步骤总结(直接上原文吧,感觉自己总结的可能会误导。。。)
字节字符串支持大多数文本字符串的内置函数
注意,将正则表达式应用于字节字符串时,正则表达式也需要是字节形式
一点需要注意的字节字符串文本字符串的差异
1.索引后得到的值为整数而不是字符
2.字节字符串输出会形如’b’xxxxxx”
3.format()不支持字节字符串
4.如果将字节字符串作为文件名,会使文件名编解码失效。
总结:
处理文本,尽量使用文本字符串!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。