《Python Cookbook(第2版)中文版》——1.18 一次完成多个替换

1.18 一次完成多个替换

任务

你想对字符串的某些子串进行替换。

解决方案

正则表达式虽然不易读懂,但有时它的确是最快的方法。re对象(标准库中的re模块)提供的强大sub方法,非常利于进行高效的正则表达式匹配替换。下面给出一个函数,该函数返回一个输入字符串的拷贝,该拷贝中的所有能够在指定字典中找到的子串都被替换为字典中的对应值:

import re
def multiple_replace(text, adict):
     rx = re.compile('|'.join(map(re.escape, adict)))
     def one_xlat(match):
           return adict[match.group(0)]
     return rx.sub(one_xlat, text)

讨论

本节展示了怎样使用Python的标准模块re来一次完成多个子串的替换。假设你有个基于字典的字符串的映射关系。字典的key就是你想要替换的子串,而字典中key的对应值则正是被用来做替代物的字符串。也可以针对字典的键值对应关系,调用字符串方法replace来完成替换,它将多次处理和创建原文本的复制,但逻辑却很清晰,速度也不错。不过re.sub的回调函数机制可以让处理方式变得更加简单。

首先,我们根据想要匹配的key创建一个正则表达式。这个正则表达式形式为a1|a2|...|aN,由N个需要被替换的字符串组成,并被竖线隔开,创建的方法也很简单,如代码所示,一行代码完成。然后,我们不直接给re.sub传递用于替换的字符串,而是传入一个回调函数参数。这样,每当遇到一次匹配,re.sub就会调用该回调函数,并将re.MatchObject的实例作为唯一参数传递给该回调函数,并期望着该回调函数返回作为替换物的字符串。在本例中,回调函数在字典中查找匹配的文本,并返回了对应值。

本节展示的函数multiple_replace,每次被调用时都会重新计算正则表达式并重新定义one_xlat辅助函数。但你经常只需要使用同一个固定不变的翻译表来完成很多文本的替换,这种情况下也许会希望只做一次准备工作。出于这种需求,也许会使用下面的基于闭包的方式:

import re
def make_xlat(args, *kwds):
     adict = dict(args, *kwds)
     rx = re.compile('|'.join(map(re.escape, adict)))
     def one_xlat(match):
           return adict[match.group(0)]
     def xlat(text):
           return rx.sub(one_xlat, text)
     return xlat

可以给make_xlat函数传递一个字典参数,或者其他的可以传递给内建的dict用于创建一个字典的参数组合;make_xlat返回一个xlat闭包,它只需要一个字符串参数text,并返回text的一个拷贝,该拷贝是根据字典给出的翻译表完成了替换之后的结果。

下面给出应用此函数的例子。通常我们可以把这个片段中的示例代码作为本节给出的代码源文件的一部分,这段代码受到前面的Python语句的保护不会被执行,除非这个模块被作为主脚本被执行:

if _ _name_ _ == "_ _main_ _":
       text = "Larry Wall is the creator of Perl"
       adict = {
          "Larry Wall" : "Guido van Rossum",
          "creator" : "Benevolent Dictator for Life",
          "Perl" : "Python",
       }
       print multiple_replace(text, adict)
       translate = make_xlat(adict)
       print translate(text)

本节中的替换任务常常是基于单词的替换任务,而不是基于任意一个子字符串。通过特殊的r’\b’序列,正则表达式可以很好地找出单词的开始和结束位置。我们可以修改multiple_replace和make_xlat中创建和分配正则表达式rx的部分,从而完成一些自定义任务:

rx = re.compile(r'\b%s\b' % r'\b|\b'.join(map(re.escape, adict)))

其余的代码和本节前面给出的一样。但是,这种代码相似性可不是好事:那意味着我们需要很多相似的版本,每个创建正则表达式的部分都有点不同,我们可能会需要做大量的复制粘贴工作,这是代码复用中最糟糕的情况,另外在未来的维护上也增加了麻烦。

编写好代码的一个关键规则是:“一次,只做一次!”当我们注意到代码重复的时候,应该能够很快嗅到“不妙”的气味,并对原来的代码进行重构以提高复用性。因此,为了便于定制,我们更需要的是一个类,而不是函数或者闭包。下面给出个例子,我们实现了一个类,功能近似于make_xlat,但却能够通过子类化和重载进行定制:

class make_xlat:
      def _ _init_ _ (self, args, *kwds):
            self.adict = dict(args, *kwds)
            self.rx = self.make_rx( )
      def make_rx(self):
            return re.compile('|'.join(map(re.escape, self.adict)))
      def one_xlat(self, match):
            return self.adict[match.group(0)]
      def _ _call_ _ (self, text):
            return self.rx.sub(self.one_xlat, text)

这是对makexlt函数的一个完全的替代:另一方面,我们在这之前展示的代码,由于有if name == ‘ main _’来保护,即使make_xlat由以前的函数变成了类,也不会有什么问题。函数更加简单快速,但是类的优势是可以通过面向对象的方法—子类化或重载某些函数,轻易地实现重新定制。为了对单词进行翻译替换,代码可以这样写:

class make_xlat_by_whole_words(make_xlat):
      def make_rx(self):
           return re.compile(r'\b%s\b' % r'\b|\b'.join(map(re.escape, self.adict)))

通过简单的子类化和重载来实现定制化,我们避免了对代码的大量的复制和粘贴,这也是有时我们宁可舍弃更加简单的函数或者闭包不用,而使用面向对象结构的原因。仅仅把相关的功能打包成一个类并不能自动实现需要的定制。要实现高度的可定制性,在把功能划分成独立的类方法时必须具有一定的前瞻性。幸好,你不用逼自己第一次就把代码写得很完美;如果代码中没有能够符合任务需求的内部结构时(在这个例子中,我们通过子类化和选择性重载来复用代码),可以而且也应该对代码进行重构,构建出符合需求的结构。当然需要进行一些合适的测试来确保没有把原有的逻辑破坏掉,与此同时,你也完成了对自己的思想内容的重构。访问http://www.refactoring.com 可以获得更多关于重构的艺术和实践的信息。

时间: 2024-10-29 12:56:01

《Python Cookbook(第2版)中文版》——1.18 一次完成多个替换的相关文章

《Python Cookbook(第2版)中文版》——导读

前 言 这本书不是一本典型的O'Reilly风格的书,而是一本集合了多个作者的手稿的作品.实际上,这也是一种将开源开发的方式应用到书籍出版业的尝试.Python社区有超过300个成员在本书中贡献了他们的心得和资料.在这里,我们作为编辑,想给你--本书的读者,介绍一些重要的背景资料,这些背景资料是关于此书是如何编著出来,以及这个过程和涉及的人,并提出一些关于这种崭新的风格的思考. 目 录 [第1章 文本1.1 每次处理一个字符](https://yq.aliyun.com/articles/963

谁有<<CLR Via C#>>第三版中文版的电子书

问题描述 谁有<<CLRViaC#>>第三版中文版的电子书,我是个初学者,看网上推荐此书的人多,想看一下,我的QQ:330784617.谢谢!! 解决方案 解决方案二:试一试我一般看英文的,虽然很少看书:(解决方案三: 解决方案四:第二版有的,想看第三版.

拒绝从入门到放弃_《Python 核心编程 (第二版)》必读目录

目录 目录 关于这本书 必看知识点 最后 关于这本书 <Python 核心编程 (第二版)>是一本 Python 编程的入门书,分为 Python 核心(其实并不核心,应该叫基础) 和 高级主题 两大部分,以 Python 2.x 作为主要演示版本,涵盖的知识面广,知识点较齐全,代码多且好理解,但对 Python 版本特性的内容太久远,不合时宜. 整体来说 Python 核心 部分是主要内容,高级主题 部分作为应用扩展内容.后半部分篇幅较短,内容不够深入,只到了解的层面,好在横向够广(每一个主

(六十二)第四章总结——《C++ Primer Plus 第6版 中文版》

书是<C++ Primer Plus  第6版  中文版> 数组.指针.结构 是C++的3种复合类型.   注:为了方便,类型名用int为主,变量名用a为主.   数组: 包括数组(例如int a[10];)和字符串(例如char a[10];),还有string类(例如string a="abc";),vector类(例如vector<int>a(5)).array类(array<int,3>a)等. 数组名表示数组所在的(第一个元素)内存地址.

求大神解答一下-C++ primer plus 第6版 中文版 第16章复习题的一个问题

问题描述 C++ primer plus 第6版 中文版 第16章复习题的一个问题 奇葩的是课后居然没答案...... 求正规.严谨.简洁的标准答案! 程序清单16.15(在p708页):functor.cpp //functor.cpp--using a functor #include尖括号iostream尖括号 #include尖括号list尖括号 #include尖括号iterator尖括号 #include尖括号algorithm尖括号 template//functor class

【转】c++.primer.plus.第五版.中文版[下载]

c++.primer.plus.第五版.中文版[下载] 一共有5部分.全部下载完才可解压阅读. c++.primer.plus.第五版.中文版(一) c++.primer.plus.第五版.中文版(二) c++.primer.plus.第五版.中文版(三) c++.primer.plus.第五版.中文版(四) c++.primer.plus.第五版.中文版(五) "在遇到无法解决的问题时,我总会求助于C++ Primer一书."--Bruce Eckel,"编程思想"

《Python Cookbook(第3版)中文版》——1.18 将名称映射到序列的元素中

1.18 将名称映射到序列的元素中 1.18.1 问题 我们的代码是通过位置(即索引,或下标)来访问列表或元组的,但有时候这会使代码变得有些难以阅读.我们希望可以通过名称来访问元素,以此减少结构中对位置的依赖性. 1.18.2 解决方案 相比普通的元组,collections.namedtuple()(命名元组)只增加了极小的开销就提供了这些便利.实际上collections.namedtuple()是一个工厂方法,它返回的是Python中标准元组类型的子类.我们提供给它一个类型名称以及相应的字

《Python Cookbook(第3版)中文版》——导读

前 言 自2008年以来,我们已经目睹了整个Python世界正缓慢向着Python 3进化的事实.众所周知,完全接纳Python 3要花很长的时间.事实上,就在写作本书时(2013年),大多数Python程序员仍然坚持在生产环境中使用Python 2.关于Python 3不能向后兼容的事实也已经做了许多努力来补救.的确,向后兼容性对于任何已经存在的代码库来说是个问题.但是,如果你着眼于未来,你会发现Python 3带来的好处绝非那么简单. 正因为Python 3是着眼于未来的,本书在之前的版本上

《Python Cookbook(第2版)中文版》——1.13 访问子字符串

1.13 访问子字符串 任务 获取字符串的某个部分.比如,你读取了一条定长的记录,但只想获取这条记录中的某些字段的数据. 解决方案 切片是个好方法,但是它一次只能取得一个字段: afield = theline[3:8] 如果还需考虑字段的长度,struct.unpack可能更适合.比如: import struct # 得到一个5字节的字符串,跳过3字节,得到两个8字节字符串,以及其余部分: baseformat = "5s 3x 8s 8s" # theline超出的长度也由这个b