Python 杂记之 实现列表的自加和左移等操作
作者: Jim Wang 公众号: 巴博萨船长
列表与操作方法
列表list是Python语言中的标准数据类型,进一步讲list是一种可变的序列类型,不可变的有tuple和range(注:Python 3.x中xrange已经不存在了)。可变序列类型,可变就是长度可以改变,所含元素数值可以改变,元素数据类型可以改变;序列意指成员有序排列,可以通过索引访问元素。既然是序列类型,就一定可以迭代(for 循环),所以列表是可迭代的对象,请注意可迭代和迭代器的差异。
列表的常用操作包括,索引,切片,改变与排序。而这几种操作要属改变最多样。改变包括,一、长度改变,成员的添加(常用方法append,insert和extend)与删除(常用方法pop与remove)。二、数值改变,通过索引访问与重新赋值来实现。
列表操作的延伸思考
由于上一篇文章是与运算符相关,所以在准备文字的时候就思索,作为标准数据类型的列表,能和它组合使用的运算符都有什么呢?今天我就和大家一起总结,共同学习一下。
一般情况下,Python中列表只能与➕操作符组合使用,前提是➕号左右都是序列类型。可能别的教程里面规定➕左右都应该是一样的列表类型。我们今天扩展一下,定义:➕号左右都是序列类型。
- 当加号➕右侧为列表,或者range()时,代码表现符合正常预期,请参看下面例子:
1 | 1, 2, 3, 4] m2 = [ |
List作为唯一的可变序列类型,而且与加号左侧数据类型一直,它的加法运算正常且容易理解。range, str与tuple是不可变的数据类型,理论上与列表相加时肯定会出错的,但是在Python 2.x 中却没有问题,结果如上。其原因为: Python 2.x 中range返回的是一个列表。而在Python 3.x 中这样写就会引发异常,参考如下代码:
1 | 1, 2, 3, 4] m2 = [ |
因为Python 3.x 中返回的是一个真正的range类实例,是一个不可变的序列类型。
- 当加号➕右侧为其它序列类型,即不可变序列类型,如 tuple 或 str 时,请参看下面例子:
1 | 1, 2, 3, 4] m2 = [ |
理论上肯定不行的,毕竟左右两侧的数据类型不同。但是总会有些意外,有些惊喜。虽然在运算符的章节中,我们了解到自加操作符与加法和赋值的组合等效,即 a += 1 等效于 a = a + 1。但是参照上方的例子,我们发现应该在解析运行的时候。两种数据操作方式还是有些不一样的,比如在参与的数据有列表是,自加时,因为列表(等号左侧)为可变对象,原则上仅需要本地对象(这里指m2)进行修改,不需要产生新的对象。那么在修改的时候解释器会讲右侧的对象尝试数据类型转换,上述的例子中讲字符穿转换为字符列表,将tuple转换为列表,进而对本地m2对象进行修改,所以我们就实现了所有序列类型的加法操作,注意非序列的数据类型不行,是不是有点神奇。
进一步延伸思考
上一节,我们通过总结可知:1. 列表的加法操作,并不是全效的加法,因为对数据只能对序列操作,那么如果仅需要添加一个成员的时候,加法操作就不再适用。2. 列表没有减法操作。那么有解决方法吗?了解到Python万物皆对象(必定不单身)。那么如果继承列表基本类,创建自有字类,通过重写方法就是实现Python的全效加法于减法。有想法之后就实现一下,代码如下:
1 | #!/usr/bin/env python |
在使用需要注意,由于Python默认的列表类为list,传统的列表赋值方式只能产生list的实例对象,那么如果初始化玩MyList类的实例后,再为实例变量使用传统方式赋值时,那么就会产生新的对象,而且新类的属性与方法将丢失。
1 | from test import MyList |
实际使用结果如下,因为在加法运算时,我们返回类实例本身,所以列表的值在做加法是会发生变化。
1 | "a", "b", "c"]) ml = MyList([ |
而在做减法运算时,我们创建并返回一个新的类实例,所以减数,即原始列表值不发生改变,请参见上述代码中带注释的内容。有心的同学可能也发现了,我们重写__iadd__(), 实现了我们预期的自加,重写__add__(), 实现了我们预期的加法,重写__sub__(), 实现了我们预期的列表减法运算和重写__lshift__(), 实现了我们预期的列表的左移运算,怎么样是不是也有一点成就感。
小结
在之前的文章中我们介绍了如何动态重写类方法,那么我们的上述的方法能不能也动态地写入基类list中,使其所有实例都有新的方法呢?感兴趣的童鞋可以自己可以试一下。我把结果写出来, 就不再举例分析。是不行的,由于基类是由别的语言实现的属于built-in类型,虽然也是动态的类,但是却因为别的语言限制了访问,所以其__iadd__()方法,在实例中是仅可读的。所以就没办法实现list基类,这些方法的动态绑定和重写。
文章首发于 Jim Wang's blog , 转载文章请务必以超链接形式标明文章出处,作者信息及本版权声明。