2.10 在正则式中使用Unicode默认情况下re模块已经对一些Unicode字符类有了基本的支持。 比如\\d已经匹配任意的unicode数字字符了import renum re.compile(‘\d’)ASCII digitsnum.match(‘123’)_sre.SRE_Match object at 0x1007d9ed0Arabic digitsnum.match(‘\u0661\u0662\u0663’)_sre.SRE_Match object at 0x101234030如果你想在模式中包含指定的Unicode字符你可以使用Unicode字符对应的转义序列(比如\uFFF或者\UFFFFFFF)。 比如下面是一个匹配几个不同阿拉伯编码页面中所有字符的正则表达式arabic re.compile(‘[\u0600-\u06ff\u0750-\u077f\u08a0-\u08ff]’)2.11 删除字符串中不需要的字符strip()方法能用于删除开始或结尾的字符。lstrip()和rstrip()分别从左和从右执行删除操作。 默认情况下这些方法会去除空白字符但是你也可以指定其他字符。比如Whitespace strippings ’ hello world \n’s.strip()‘hello world’s.lstrip()‘hello world \n’s.rstrip()’ hello world’Character strippingt ‘-----hello ’t.lstrip(’-‘)‘hello ’t.strip(’-’)‘hello’这些strip()方法在读取和清理数据以备后续处理的时候是经常会被用到的。 比如你可以用它们来去掉空格引号和完成其他任务。但是需要注意的是去除操作不会对字符串的中间的文本产生任何影响。比如s ’ hello world \n’s s.strip()s‘hello world’如果你想处理中间的空格那么你需要求助其他技术。比如使用replace()方法或者是用正则表达式替换。示例如下s.replace(’ ‘, ‘’)‘helloworld’import rere.sub(’\s, ’ , s)‘hello world’通常情况下你想将字符串strip操作和其他迭代操作相结合比如从文件中读取多行数据。 如果是这样的话那么生成器表达式就可以大显身手了。比如with open(filename) as f:lines (line.strip() for line in f)for line in lines:print(line)在这里表达式lines (line.strip() for line in f)执行数据转换操作。 这种方式非常高效因为它不需要预先读取所有数据放到一个临时的列表中去。 它仅仅只是创建一个生成器并且每次返回行之前会先执行strip操作。生成器表达式最大的价值在于无论文件有多大比如 10GB这段代码都能流畅运行因为它每次只在内存里处理一行数据。lines…这个即生成器表达式,这一行代码运行时并没有真正读取文件内容也没有做strip操作。只是定义一个配方一个规则当下面调用for line in lines的时候才会真正执行2.12 审查清理文本字符串可以使用经常会被忽视的str.translate()方法。 为了演示假设你现在有下面这个凌乱的字符串s ‘pýtĥöñ\fis\tawesome\r\n’s‘pýtĥöñ\x0cis\tawesome\r\n’第一步是清理空白字符。为了这样做先创建一个小的转换表格然后使用translate()方法translate的语法就是创建一个转换表格remap把对应\t换成对应的空格remap {… ord(‘\t’) : ’ ‘,… ord(’\f’) : ’ ‘,… ord(’\r’) : None # Deleted… }a s.translate(remap)a‘pýtĥöñ is awesome\n’正如你看的那样空白字符\t和\f已经被重新映射到一个空格。回车字符r直接被删除。2.13字符串对齐对于基本的字符串对齐操作可以使用字符串的ljust(),rjust()和center()方法。**ljust()即填空在右侧**rjust()即填空在左侧center即把原本的字符放中间比如text ‘Hello World’text.ljust(20)‘Hello World ’text.rjust(20)’ Hello World’text.center(20)’ Hello World ’所有这些方法都能接受一个可选的填充字符。比如text.rjust(20,‘’)’ Hello World’text.center(20,’ * )’ *** * Hello World ** ** * ’函数format()同样可以用来很容易的对齐字符串。 你要做的就是使用,或者^字符后面紧跟一个指定的宽度。format 20即在左侧补空白或别的字符比如format(text, ‘20’)’ Hello World’format(text, ‘20’)Hello World ’format(text, ‘^20’)’ Hello World ’如果你想指定一个非空格的填充字符将它写到对齐字符的前面即可format(text, ‘20s’)’ Hello World’format(text, ‘*^20s’)’ ** ** Hello World * *** * ’2.14合并拼接字符串如果你想要合并的字符串是在一个序列或者iterable中那么最快的方式就是使用join()方法。比如parts [‘Is’, ‘Chicago’, ‘Not’, ‘Chicago?’]’ .join(parts)‘Is Chicago Not Chicago?’‘,’.join(parts)‘Is,Chicago,Not,Chicago?’‘’.join(parts)‘IsChicagoNotChicago?’join的调用方法aa.join(bb)aa是连接符bb是待填入的字符2.15字符串中插入变量Python并没有对在字符串中简单替换变量值提供直接的支持。 但是通过使用字符串的format()方法来解决这个问题。比如s ‘{name} has {n} messages.’s.format(name‘Guido’, n37)‘Guido has 37 messages.’或者如果要被替换的变量能在变量域中找到 那么你可以结合使用format_map()和vars()。就像下面这样name ‘Guido’n 37s.format_map(vars())‘Guido has 37 messages.’vars()还有一个有意思的特性就是它也适用于对象实例。比如class Info:… definit(self, name, n):… self.name name… self.n n…a Info(‘Guido’,37)s.format_map(vars(a))‘Guido has 37 messages.’2.16 以指定列宽格式化字符串使用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.”下面演示使用textwrap格式化字符串的多种方式import textwrapprint(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 aroundthe 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 myeyes, the eyes, the eyes, the eyes, notaround the eyes, don’t look around theeyes, 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, notaround the eyes, don’t look aroundthe eyes, look into my eyes, you’reunder.2.17 在字符串中处理html和xml你想将HTML或者XML实体如entity;或#code;替换为对应的文本。 再者你需要转换文本中特定的字符(比如, , 或 )。如果你想替换文本字符串中的 ‘’ 或者 ‘’ 使用html.escape()函数可以很容易的完成。比如s ‘Elements are written as “text”.’import htmlprint(s)Elements are written as “text”.print(html.escape(s))Elements are written as “tagtext/tag”.Disable escaping of quotesprint(html.escape(s, quoteFalse))Elements are written as “tagtext/tag”.如果你正在处理的是ASCII文本并且想将非ASCII文本对应的编码实体嵌入进去 可以给某些I/O函数传递参数errorsxmlcharrefreplace来达到这个目。比如s ‘Spicy Jalapeño’s.encode(‘ascii’, errors‘xmlcharrefreplace’)b’Spicy Jalapeño’为了替换文本中的编码实体你需要使用另外一种方法。 如果你正在处理HTML或者XML文本试着先使用一个合适的HTML或者XML解析器。 通常情况下这些工具会自动替换这些编码值你无需担心。有时候如果你接收到了一些含有编码值的原始文本需要手动去做替换 通常你只需要使用HTML或者XML解析器的一些相关工具函数/方法即可。比如s ‘Spicy Jalapeñoquot.’from html.parser import HTMLParserp HTMLParser()p.unescape(s)‘Spicy “Jalapeño”.’t ‘The prompt is ’from xml.sax.saxutils import unescapeunescape(t)‘The prompt is ’2.18 字符串令牌解析令牌流就是将一长串原本无意义的字符序列转化为一组有特定含义的、结构化的数据列表解决方案假如你有下面这样一个文本字符串text ‘foo 23 42 * 10’为了令牌化字符串你不仅需要匹配模式还得指定模式的类型。 比如你可能想将字符串像下面这样转换为序列对tokens [(‘NAME’, ‘foo’), (‘EQ’,‘’), (‘NUM’, ‘23’), (‘PLUS’,‘’),(‘NUM’, ‘42’), (‘TIMES’, ‘*’), (‘NUM’, ‘10’)]为了执行这样的切分第一步就是像下面这样利用命名捕获组的正则表达式来定义所有可能的令牌包括空格import reNAME r’(?P[a-zA-Z_][a-zA-Z_0-9]*)’NUM r’(?P\d)’PLUS r’(?P)’TIMES r’(?P*)’EQ r’(?P)’WS r’(?P\s)’master_pat re.compile(‘|’.join([NAME, NUM, PLUS, TIMES, EQ, WS]))在上面的模式中?PTOKENNAME用于给一个模式命名供后面使用。下一步为了令牌化使用模式对象很少被人知道的scanner()方法。 这个方法会创建一个scanner对象 在这个对象上不断的调用match()方法会一步步的扫描目标文本每步一个匹配。 下面是演示一个scanner对象如何工作的交互式例子scanner master_pat.scanner(‘foo 42’)scanner.match()_sre.SRE_Match object at 0x100677738_.lastgroup, _.group()(‘NAME’, ‘foo’)scanner.match()_sre.SRE_Match object at 0x100677738_.lastgroup, _.group()(‘WS’, ’ )scanner.match()_sre.SRE_Match object at 0x100677738_.lastgroup, _.group()(‘EQ’, ‘’)scanner.match()_sre.SRE_Match object at 0x100677738_.lastgroup, _.group()(‘WS’, ’ )scanner.match()_sre.SRE_Match object at 0x100677738_.lastgroup, _.group()(‘NUM’, ‘42’)scanner.match()2.19 实现一个简单的递归下降分析器你想根据一组语法规则解析文本并执行命令或者构造一个代表输入的抽象语法树。 如果语法非常简单你可以自己写这个解析器而不是使用一些框架。解决方案在这个问题中我们集中讨论根据特殊语法去解析文本的问题。 为了这样做你首先要以BNF或者EBNF形式指定一个标准语法。 比如一个简单数学表达式语法可能像下面这样expr :: expr term| expr - term| termterm :: term * factor| term / factor| factorfactor :: ( expr )| NUM或者以EBNF形式expr :: term { (|-) term }*term :: factor { (|/) factor }factor :: ( expr )| NUM在EBNF中被包含在{...}*中的规则是可选的。*代表0次或多次重复(跟正则表达式中意义是一样的)。现在如果你对BNF的工作机制还不是很明白的话就把它当做是一组左右符号可相互替换的规则。 一般来讲解析的原理就是你利用BNF完成多个替换和扩展以匹配输入文本和语法规则。 为了演示假设你正在解析形如3 4 * 5的表达式。 这个表达式先要通过使用2.18节中介绍的技术分解为一组令牌流。 结果可能是像下列这样的令牌序列NUM NUM * NUM在此基础上 解析动作会试着去通过替换操作匹配语法到输入令牌exprexpr :: term { (|-) term }*expr :: factor { (|/) factor }{ (|-) term }*expr :: NUM { (|/) factor }{ (|-) term }*expr :: NUM { (|-) term }*expr :: NUM term { (|-) term }*expr :: NUM factor { (|/) factor }{ (|-) term }*expr :: NUM NUM { (|/) factor}{ (|-) term }*expr :: NUM NUM * factor { (|/) factor }{ (|-) term }*expr :: NUM NUM * NUM { (|/) factor }{ (|-) term }*expr :: NUM NUM * NUM { (|-) term }*expr :: NUM NUM * NUM下面所有的解析步骤可能需要花点时间弄明白但是它们原理都是查找输入并试着去匹配语法规则。 第一个输入令牌是NUM因此替换首先会匹配那个部分。 一旦匹配成功就会进入下一个令牌以此类推。 当已经确定不能匹配下一个令牌的时候右边的部分(比如{ (*/) factor }*)就会被清理掉。 在一个成功的解析中整个右边部分会完全展开来匹配输入令牌流。2.20 字节字符串上的字符串操作首先什么是字节字符串字节字符串是 Python 3 中的一种数据类型用于表示二进制数据而不是文本字符。简单来说它可以被理解为“计算机底层存储的原始数据一串数字”。为了让你更直观地理解我们可以从以下几个方面来对比1. 核心区别它存的是什么普通字符串存储的是字符如‘a’, ‘中’, ‘!’。它是抽象的不关心计算机具体怎么存这些字只关心它代表了什么含义。本质给人看的。字节字符串存储的是字节即 0 到 255 之间的整数。它是具体的代表计算机硬盘或内存里实际存储的 0 和 1 的序列。本质给机器处理的。2. 直观的例子当你创建一个字节字符串data bHello时Python 实际上存储的是这几个字符对应的 ASCII 码数值想在字节字符串上执行普通的文本操作(比如移除搜索和替换)。解决方案字节字符串同样也支持大部分和文本字符串一样的内置操作。比如data b’Hello World’data[0:5]b’Hello’data.startswith(b’Hello’)Truedata.split()[b’Hello’, b’World’]data.replace(b’Hello’, b’Hello Cruel’)b’Hello Cruel World’这些操作同样也适用于字节数组。比如data bytearray(b’Hello World’)data[0:5]bytearray(b’Hello’)data.startswith(b’Hello’)Truedata.split()[bytearray(b’Hello’), bytearray(b’World’)]data.replace(b’Hello’, b’Hello Cruel’)bytearray(b’Hello Cruel World’)你可以使用正则表达式匹配字节字符串但是正则表达式本身必须也是字节串。比如data b’FOO:BAR,SPAM’import rere.split(‘[:,]’,data)Traceback (most recent call last):File “”, line 1, inFile “/usr/local/lib/python3.3/re.py”, line 191, in splitreturn _compile(pattern, flags).split(string, maxsplit)TypeError: can’t use a string pattern on a bytes-like objectre.split(b’[:,]‘,data) # Notice: pattern as bytes[b’FOO’, b’BAR’, b’SPAM’]讨论大多数情况下在文本字符串上的操作均可用于字节字符串。 然而这里也有一些需要注意的不同点。首先字节字符串的索引操作返回整数而不是单独字符。比如a ‘Hello World’ # Text stringa[0]‘H’a[1]‘e’b b’Hello World’ # Byte stringb[0]72b[1]101这种语义上的区别会对于处理面向字节的字符数据有影响。第二点字节字符串不会提供一个美观的字符串表示也不能很好的打印出来除非它们先被解码为一个文本字符串。比如s b’Hello World’print(s)b’Hello World’ # Observe b’…’print(s.decode(‘ascii’))Hello World类似的也不存在任何适用于字节字符串的格式化操作b’%10s %10d %10.2f’ % (b’ACME’, 100, 490.1)Traceback (most recent call last):File “”, line 1, inTypeError: unsupported operand type(s) for %: ‘bytes’ and ‘tuple’b’{} {} {}‘.format(b’ACME’, 100, 490.1)Traceback (most recent call last):File “”, line 1, inAttributeError: ‘bytes’ object has no attribute ‘format’如果你想格式化字节字符串你得先使用标准的文本字符串然后将其编码为字节字符串。比如‘{:10s} {:10d} {:10.2f}’.format(‘ACME’, 100, 490.1).encode(‘ascii’)b’ACME 100 490.10’