第二章开始介绍了列表这种数据结构,这个在python是经常用到的结构
列表的推导,将一个字符串编程一个列表,有下面的2种方法。其中第二种方法更简洁。可读性也比第一种要好
str=‘abc‘ string=[] for s in str: print string.append(s) ret=[s for s in str] print ret
用这种for…in的方法来推导列表,有个好处就是不会有变量泄露也就是越界的问题。这在c语言中是需要特别注意的问题。
对于两个以上的列表推导作者用到了笛卡尔积的例子。笛卡尔又称为直积。可以理解为两个集合的全排列。依然是两种表达式。
colors=[‘black‘,‘white‘] sizes=[‘s‘,‘m‘,‘l‘] tshirts=[(color,size) for color in colors for size in sizes] print tshirts for color in colors: for size in sizes: print (color,size)
列表的推导还有一种方式,称为生成式表达式。表达式都差不多,不过是方括号编程了圆括号而已:生成器的好处是什么呢。列表推导是首先生成一个6个组合的列表。这会占用到内存。而生成式则是在每一个for循环运行时才生成一个组合。这样就不会预先占用内存。
colors=[‘black‘,‘while‘] sizes=[‘s‘,‘m‘,‘l‘] for tshirt in (‘%s,%s‘ % (c,s) for c in colors for s in sizes): print tshirt
这个数据比较小,还无法看出来,举下面的例子。有10000个数组的列表。分别用列表推导法和生成式表达法来进行遍历。并用memory_profiler来监控代码占用的内存
from memory_profiler import profile
@profile def fun_try(): test=[] for i in range(10000): test.append(i) for num in (t for t in test): print num
首先是生成式的结果,可以看到在for num in (t for t in test)中没有内存增加
改成列表推导后的结果。可以看到[t for t in test]增加了0.1M的内存。
下面介绍下元组。说到元组,第一个反应就应该是不可变列表。但作者同时还介绍了元组的很多其他特性。首先来看下元组的拆包
如果有一个元组(33.9,118)分别表示经纬度。需要对经纬度进行分别赋值。代码如下。这就是元组拆包的运用。这个实现原理其实就是将可迭代对象里的元素一并赋值到对应的变量组成的元组中。
location=(33.9,118)
latitude,longitude=location
print latitude,longitude
如果在进行拆包的同时,并不是对所有的元组数据都感兴趣。_占位符就能帮助处理这种情况。像下面的代码。分隔符为/。第一个应该是abc,第二个是def。我们只需要第二个元素,因此用_,second=str.split(‘/‘),这样就只会得到第二个元素。
str=‘abc/def‘
_,second=str.split(‘/‘) print second
上面元组拆包的时候是对可迭代对象进行遍历,然后一一赋值到变量。但是如果想通过给每个元组元素的命名来访问,则需用到命名元组namdtuple。具体用法参加第一章的介绍。
序列的操作:
1 增量赋值:
增量运算符+,*等其实是调用__iadd__/__add__/__mul__方法。对于可变对象来说,+调用的是__iadd__,对于不可变对象来说对象调用的是__add__。两者有什么区别呢。先看下面的例子
str=[1,2,3] str1=(1,2,3) print "str:%d" % id(str) print "str1:%d" % id(str1) str+=str str1+=str1 print "str:%d" % id(str) print "str1:%d" % id(str1)
得到的结果如下:
str:38078184
str1:24035000
str:38078184
str1:25286840
str是列表,str1是元组,列表是可变对象,元组是不可变对象。在进行加法运算后,str的id没有改变,因此还是之前的对象,但是str1的id却发生了改变,不是之前的对象了。这种的差别在于__iadd__的方法类似调用a.extend(b)的方式,是在原有的对象上进行扩展操作。但是__add__的方式类似于a=a+b。首先a+b得到一个新的的对象,然后复制给a,因此变量和之前的对象没有任何联系。而是被关联到一个新的对象。同样的乘法__imul__/__mul__也是类似的道理
列表组成的列表
board=[[‘_‘]*3 for i in range(3)] print board board[1][2]=‘x‘ print board
首先建立了3个全为_的列表,然后对第二个列表的第3个元素进行赋值。结果如下
[[‘_‘, ‘_‘, ‘_‘], [‘_‘, ‘_‘, ‘_‘], [‘_‘, ‘_‘, ‘_‘]]
[[‘_‘, ‘_‘, ‘_‘], [‘_‘, ‘_‘, ‘x‘], [‘_‘, ‘_‘, ‘_‘]]
输出3个列表的ID
print id(board[0]) print id(board[1]) print id(board[2])
分别属于不同的ID。
38919784
38090472
38090752
我们再来看下另外一种用法。下面的代码对一个包含3个列表的列表进行*3的操作。
board=[[‘_‘]*3]*3 print board board[1][2]=‘x‘ print board
得到的结果如下:可以看到首先也是生成了3个列表,但是赋值的时候,所有列表的第3个元素都被赋值了。
[[‘_‘, ‘_‘, ‘_‘], [‘_‘, ‘_‘, ‘_‘], [‘_‘, ‘_‘, ‘_‘]]
[[‘_‘, ‘_‘, ‘x‘], [‘_‘, ‘_‘, ‘x‘], [‘_‘, ‘_‘, ‘x‘]]
我们继续看下对象的ID
print id(board[0]) print id(board[1]) print id(board[2])
发现id都一样。说明了全部指向的是同一个对象。
39122904
39122904
39122904
这种实现的方法和下面的一样,相当于首先初始化好row,然后在board中对同一个row进行插入。
row=[‘_‘]*3 board=[] for i in range(3): board.append(row)
而board=[[‘_‘]*3 for i in range(3)]的操作类似如下,在每次迭代的对象中,都新生成一个row。然后将不同的row插入到board中去。
board=[] for i in range(3): row=[‘_‘]*3 board.append(row)
如果对不可变对象中的可变对象进行赋值会产生什么后果,比如下面的这段代码
t=(1,2,[30,40]) t[2]+=[50,60] print t
是直接抛出异常呢还是赋值成功。 我们在http://www.pythontutor.com对这个代码可视化看下。首先第一步初始化t之后,得到的结果如下,t中包含一个元组。其中第3个元组元素指向一个列表
第二步对元组中的列表进行赋值后,可以看到下图中列表的元素被更新了,但同时也报了异常。
我们将代码修改成如下再执行下
t=(1,2,[30,40]) t[2].append([50,60]) print t
第一步都一样,我们从第二步开始查看。可以看到这个时候没有抛出异常,从结构来看是在[30,40]这个列表后面继续插入了一个列表。
最终得到的结果是(1, 2, [30, 40, [50, 60]])
为什么这两种实现会带来不同的结果呢。原因在于t是一个元组属于不可变对象。但用t[2]+=[50,60]的时候是对一个元组进行赋值。所以报错误。但是同时t[2]又属于一个列表可变对象。因此数据也更新成功了
但是如果用t[2].append([50,60])的操作则是对一个列表进行操作,而并没有对一个元组进行赋值。因此能够更新成功且不会报错误。这是一个很有趣的例子。对于理解可变对象和不可变对象的操作很有帮助
数组:
在列表和元组中,存放的是具体的对象,如整数对象,字符对象。如下面的整数b。占据12个字节。因为存放的是整数对象,而非整数本身
b=1 print sys.getsizeof(b)
E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter2.py
12
对于存放大量数据来说。我们选择用数组的形式要好很多。因为数组存储的不是对象,而是数字的机器翻译。也就是字节表述。和C语言中的数组是一个道理
在array中需要规定各个字符的类型,如上表中的Type code。定义好类型后则数组内的元素必须全是这个类型,否则会报错。就像下面的代码。类型规定的是b也就是单字节的整数。但是在插入的时候却是字符,则报错:TypeError: an integer is required
num=array(‘b‘) num.append(‘c‘)
E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter2.py
Traceback (most recent call last):
File "E:/py_prj/fluent_python/chapter2.py", line 18, in <module>
num.append(‘c‘)
TypeError: an integer is required
在上表中,每个类型都有字节大小的限制。如果超出了字节大小的限制也是会报错的。还是b的这个类型,是有符号的单字节整数,那么范围是-128到127.如果我们插入128.则报错:signed char is greater than maximum 提示超过了最大
num=array(‘b‘) num.append(128)
E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter2.py
Traceback (most recent call last):
File "E:/py_prj/fluent_python/chapter2.py", line 18, in <module>
num.append(128)
OverflowError: signed char is greater than maximum
对于数组这种结构体来说,由于占用的内存小,因此在读取和写入文件的时候的速度更快,相比于列表来说的话。下面来对比下:
首先是用列表生成并写入txt文档的用法
def arry_try_list(): floats=[float for float in range(10**7)] fp=open(‘list.txt‘,‘wb‘) start=time.clock() for f in floats: fp.write(str(f)) fp.close() end=time.clock() print end-start
E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter2.py
7.83854664189
总共耗时了7秒
再来看数组的形式:
def array_try(): floats=array(‘d‘,(random() for i in range(10**7))) start=time.clock() fp=open(‘floats.bin‘,‘wb‘) floats.tofile(fp) fp.close() end=time.clock() print end-start
E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter2.py
0.883260677006
耗时0.88秒。速度提高了9倍
再来对比下写入的速度:
def array_try(): floats=array(‘d‘) start=time.clock() fp=open(‘floats.bin‘,‘rb‘) floats.fromfile(fp,10**7) fp.close() end=time.clock() print end-start
E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter2.py
0.0515373089318
数组的方式只用了0.05秒,也是非常的快
双向队列
在列表或者数组中,可以用append,pop来模拟栈或者队列的操作。本章作者介绍了双向队列,在进行数组的移动时更为高效
dq=deque(range(10),maxlen=10) print dq
deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10) dq.rotate(3) #将最右边的3个元素移到左边 print dq
deque([7, 8, 9, 0, 1, 2, 3, 4, 5, 6], maxlen=10) dq.rotate(-3) #将最左边的3个元素移到右边 print dq
deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10) num=dq.popleft() #最左边的元素移除 print dq
deque([1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10) dq.appendleft(num) #将元素插入到最左边 print dq
deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
时间: 2024-10-08 02:51:11