本节书摘来自异步社区《python cookbook(第2版)中文版》一书中的第1章,第1.13节,作者[美]alex martelli , anna martelli ravenscrof , david ascher ,高铁军 译,更多章节内容可以访问云栖社区“异步社区”公众号查看。
任务
获取字符串的某个部分。比如,你读取了一条定长的记录,但只想获取这条记录中的某些字段的数据。
解决方案
切片是个好方法,但是它一次只能取得一个字段:
如果还需考虑字段的长度,struct.unpack可能更适合。比如:
如果想跳过“其余部分”,只需要给出正确的长度,拆解出theline的开头部分的数据即可:
如果需要获取5字节一组的数据,可以利用带列表推导(lc)的切片方法,代码很简单:
将字符切成一个个单独的字符更加容易:
如果想把数据切成指定长度的列,用带lc的切片方法通常是最简单的:
在lc中调用zip,返回的是一个列表,其中每项都是形如(cuts[k], cuts[k+1])这样的数对,除了第一项和最后一项,这两项分别是(0, cuts[0])和(cuts[len(cuts)-1], none)。换句话说,每一个数对都给出了用于切割的正确的(i, j),仅有第一项和最后一项例外,前者给出的是切割之前的切片方式,后者给出的是切割完成之后到字符串末尾的剩余部分。lc利用这些数对就可以正确地将theline切分开来。
讨论
本节受到了perl cookbook 1.1的启发。python的切片方法,取代了perl的substr。perl的内建的unpack和python的struct.unpack也非常相似。不过perl的手段更丰富一点,它可以用*来指定最后一个字段长度,并指代剩余部分。在python中,无论是为了获取或者跳过某些数据,我们都得计算和插入正确的长度。不过这不是什么大问题,因为此类抽取字段数据的任务往往可以被封装成小函数。如果该函数需要反复被调用的话,memoizing,通常也被称为自动缓存机制,能够极大地提高性能,因为它避免了为struct.unpack反复做一些格式准备工作。参见第18.5节中关于memoizing的更多细节。
在纯python的环境中,struct.unpack作为字符串切片的一种替代方案,非常好用(当然不能和perl的substr比,虽然它不接受用*指定的区域长度,但仍是值得推荐的好东西)。
这些代码片段,最好被封装成函数。封装的一个优点是,我们不需要每次使用时都计算最后一个区域的长度。下面的函数基本上等价于“解决方案”小节给出的直接使用struct.unpack的代码片段:
一个值得注意(或者说值得批评)的设计是该函数提供了lastfield=false这样一个可选参数。这基于一个经验,虽然我们常常需要跳过最后的长度未知的部分,有时候我们还是需要获取那段数据。采用lastfield and s or x(等同于c语言中的三元运算符,lastfield?"s":"c")这样的表达式,我们省去了一个if/else,不过是否需要为这点紧凑牺牲可读性还有值得商榷之处。参看第18.9节中有关在python中模拟三元运算符的内容。
若fields函数在一个循环内部被调用,使用元组(baseformat, len(theline), lastfield)作为key来充分利用memoizing机制将极大地提高性能。这里给出一个使用memoizing机制的fields版本:
这种利用缓存的方法,目的是将比较耗时的格式准备工作一次完成,并存储在_cache字典中。当然,正像所有的优化措施一样,这种采用了缓存机制的优化也需要通过测试来确定究竟能在多大程度上提高性能。对这个例子,我的测试结果是,通过缓存优化的版本要比优化之前快约30%到40%,换句话说,如果这个函数不是你的程序的性能瓶颈部分,其实没有什么必要多此一举。
“解决方案中”给出的另一个关于 lc的代码片段,也可以封装成函数:
对最后一个代码片段的封装:
在上面这些例子中,利用列表推导来切片要比用struct.unpack略好一些。
用生成器可以实现一个完全不同的方式,像这样:
当需要循环遍历获取的结果序列时,无论是显式调用,还是借助一些可调用的“累加器”,比如’’.join来进行隐式调用,基于生成器的方式都会更加合适。如果需要的是各字段数据的列表,你手上得到的结果却是一个生成器,可以调用内建的list来完成转化,像这样: