《Python Cookbook(第2版)中文版》——1.15 扩展和压缩制表符

本节书摘来自异步社区《Python Cookbook(第2版)中文版》一书中的第1章,第1.15节,作者[美]Alex Martelli , Anna Martelli Ravenscrof , David Ascher ,高铁军 译,更多章节内容可以访问云栖社区“异步社区”公众号查看。

1.15 扩展和压缩制表符

任务

将字符串中的制表符转化成一定数目的空格,或者反其道而行之。
解决方案

将制表符转换为一定数目的空格是一种很常见的需求,用Python的字符串提供的expandtabs方法可以轻松解决问题。由于字符串不能被改变,这个方法返回的是一个新的字符串对象,是原字符串的一个修改过的拷贝。不过,仍可以将修改过的拷贝绑定到原字符串的名字:

mystring = mystring.expandtabs( )

这样并不会改变mystring原先指向的字符串对象,只不过将名字mystring绑定到了一个新创建的修改过的字符串拷贝上了,该字符串拷贝中的制表符也已经被扩展为一些空格了。对expandtabs来说,默认情况下,制表符的宽度为8;可以给expandtabs传递一个整数参数来指定新的制表符宽度。

将空格转成制表符则比较少见和怪异。如果真的想要压缩制表符,最好还是用别的办法来解决,因为Python没有提供一个内建的方法来“反扩展”空格,将其转化为制表符。当然,我们可以自己写个函数来完成任务。字符串的切分、处理以及重新拼接,往往比对整个字符串反复转换要快得多:

def unexpand(astring, tablen=8):
      import re
      # 切分成空格和非空格的序列
      pieces = re.split(r'( +)', astring.expandtabs(tablen))
      # 记录目前的字符串总长度
      lensofar = 0
      for i, piece in enumerate(pieces):
              thislen = len(piece)
              lensofar += thislen
              if piece.isspace( ):
                     # 将各个空格序列改成tabs+spaces
                     numblanks = lensofar % tablen
                     numtabs = (thislen-numblanks+tablen-1)/tablen
                     pieces[i] = '\t'*numtabs + ' '*numblanks
      return ''.join(pieces)

例子中的unexpand函数,只适用于单行字符串;要处理多行字符串,用’’. Join ([ unexpand(s) for s in astring.splitlines(True) ])即可。

讨论

虽然在Python的字符串操作中,正则表达式从来不是必不可少的部分,但有时它真的很方便。正如代码所示,unexpand函数利用了re.split相对于字符串的split额外提供的特性:当正则表达式包含了一个括弧组时,re.split返回了一个list列表,列表中的每两个相邻的分隔片段之间都被插入了一个用于分隔的“分隔器”。这样,我们就得到了一个pieces列表,所有的连续空白字符串和非空白字符串都成为了它的子项;接着我们在for循环中持续跟踪已处理字符串的长度,并将所有的空白字符片段尽可能地转换成相应的制表符,最后加上必要的剩余空格以保持总体的长度。

对于很多编程任务来说,扩展制表符并不是简简单单地调用expandtabs方法。比如,需要整理一些Python源代码,这些代码写得不是很规范,不只用空格来控制缩进(只用空格是最好的方式),而是采取了制表符和空格的混合方式(这是非常糟糕的做法)。这实际上加剧了复杂性,比如,需要猜测制表符的宽度(采用标准的4个空格的缩进方式是值得强烈推荐的)。另外,你可能还需要保留一些在字符串内部的并非用于控制缩进的制表符(有人可能错误地使用了实际的制表符,而不是“t”,来指代字符串中间的制表字符),甚至你还可能需要对具有不同意义的文本做不同的处理。在某些情况下,问题还不算棘手,比如,假设只需要处理每行文本行首的空白符,而无须理会其他的制表符。一个像下面这样的运用正则表达式的小函数就足够了:

def expand_at_linestart(P, tablen=8):
      import re
      def exp(mo):
            return mo.group( ).expand(tablen)
      return ''.join([ re.sub(r'^\s+', exp, s) for s in P.splitlines(True) ])

expand_at_linestart函数充分利用了re.sub函数,re.sub在一个字符串中搜寻符合其正则表达式描述的片段,每当它找到一个匹配,便将匹配的字符串作为一个参数传递给一个函数,并调用该函数返回一个替换匹配字符串的字符串。为了方便,expand_at_linestart被设计为可以处理多行文本字符串P,并对调用splitlines的结果使用了列表推导处理,最后’n’.join将完成后的各行字符串拼接起来。当然,这样的设计完全也可用于单行文本字符串。

实际的制表符扩展的任务可能会很特殊,比如需要考虑制表符是在一个字符串的中间还是外部,是处于哪种类型的文本中(比如,源代码中的注释文本和代码文本),但不管怎么样,至少都需要做一个断词的工作。另外,也可能需要对待处理的源代码进行一个完全的解析,而不是简单地依赖一些字符串和正则表达式操作。如果你想完成的任务具有这种要求,其工作量会相当可观。可以仔细阅读第16章,那一章的内容对初学者有很大帮助。

当你汗流浃背地最终完成了转化任务,一定能深深体会到,不管是写代码还是编辑代码,一定要遵循那种常用的、被推荐的Python编码风格:只使用空格、4个空格缩进一级、不用制表符、在字符串中间的制表符应该用“t”,而不是实际的制表符。你喜爱的Python编辑器也应该被强化支持这些使用习惯,这样在保存Python源代码文件时你能得到一个遵守约定的格式;比如,IDLE(Python所带的免费的集成开发环境)附带的编辑器就完全支持这些约定。最好能够在问题出现之前就配置好你的编辑器,而不是在问题出现之后采取弥补措施。

更多文档

Library Reference的“序列类型”一节下的字符串expandtabs方法;Perl Cookbook 1.7;Library Reference和Python in a Nutshell文档中的re模块。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值