[toc]html
第二章开始介绍了列表这种数据结构,这个在python是常常用到的结构python
列表的推导,将一个字符串编程一个列表,有下面的2种方法。
其中第二种方法更简洁。可读性也比第一种要好。编程
str = 'abc' string = [] # 第一种方法 for s in str: print(string.append(s)) # 第二种方法 ret = [s for s in str] print(ret)
用这种for…in的方法来推导列表,有个好处就是不会有变量泄露也就是越界的问题
列表的推导还有一种方式,称为生成器表达式。
表达式都差很少,不过是方括号编程了圆括号而已数组
生成器的好处是什么呢?
数据结构
列表推导是首先生成一个组合的列表,这会占用到内存。
而生成式则是在每个for循环运行时才生成一个组合,这样就不会预先占用内存
生成器表达式不会一次将整个列表加载到内存之中,而是生成一个生成器对象(Generator objector),因此一次只加载一个列表元素
举个例子来讲明:app
有20000个数组的列表。分别用列表推导法和生成式表达法来进行遍历。
并用memory_profiler
来监控代码占用的内存dom
列表推导法例子:性能
# 若是系统没有这个模块就执行pip install memory_profiler进行安装 from memory_profiler import profile @profile def fun_try(): test = [] for i in range(20000): test.append(i) for num in [t for t in test]: print(num) fun_try()
这个代码运行结果是:网站
Line # Mem usage Increment Line Contents ================================================ 5 39.9 MiB 39.9 MiB @profile 6 def fun_try(): 7 39.9 MiB 0.0 MiB test = [] 8 9 40.8 MiB 0.0 MiB for i in range(20000): 10 40.8 MiB 0.1 MiB test.append(i) 11 12 41.2 MiB 0.2 MiB for num in [t for t in test]: 13 41.2 MiB 0.0 MiB print(num)
生成式例子:ui
# 若是系统没有这个模块就执行pip install memory_profiler进行安装 from memory_profiler import profile @profile def fun_try(): test = [] for i in range(20000): test.append(i) for num in (t for t in test): print(num) fun_try()
这个代码运行结果是:
Line # Mem usage Increment Line Contents ================================================ 5 40.1 MiB 40.1 MiB @profile 6 def fun_try(): 7 40.1 MiB 0.0 MiB test = [] 8 9 41.1 MiB 0.0 MiB for i in range(20000): 10 41.1 MiB 0.1 MiB test.append(i) 11 12 41.1 MiB 0.0 MiB for num in (t for t in test): 13 41.1 MiB 0.0 MiB print(num)
结论:
`经过这两个结果能够看到列表推导法增长了0.2MB的内存
除非特殊的缘由,应该常常在代码中使用生成器表达式。
但除非是面对很是大的列表,不然是不会看出明显区别的。
`
下面介绍下元组。说到元组,第一个反应就应该是不可变列表。
但做者同时还介绍了元组的不少其余特性。
首先来看下元组的拆包。
#元组拆包 t = (20, 8) a, b = t print(a, b)
若是在进行拆包的同时,并非对全部的元组数据都感兴趣。
_占位符就能帮助处理这种状况
import os _, filename = os.path.split("/home/jian/prj/demo.txt") print(_, filename) #/home/jian/prj demo.txt
上面元组拆包的时候是对可迭代对象进行遍历,而后一一赋值到变量。
可是若是想经过给每一个元组元素的命名来访问,则需用到命名元组namdtuple
# collections.namedtuple构建一个带有字段名的元组和一个有名字的类 from collections import namedtuple, OrderedDict City = namedtuple('City', 'name country population') tokyo = City('Tokyo', 'JP', '123') tokyo_data = ('Tokyo', 'JP', '123') print(tokyo) # City(name='Tokyo', country='JP', population='123') print(tokyo.name, tokyo.country, tokyo.population) # Tokyo JP 123 print(City._fields) # ('name', 'country', 'population') tokyo = City._make(tokyo_data) print(tokyo) # City(name='Tokyo', country='JP', population='123') OrderedDict([('name', 'Tokyo'), ('country', 'JP'), ('population', '123')]) print(tokyo._asdict()) # OrderedDict([('name', 'Tokyo'), ('country', 'JP'), ('population', '123')])
*符号用来处理剩下的元素
a, b, *rest = range(5) print(a, b, rest) # 0 1 [2, 3, 4] a, *b, rest = range(5) print(a, b, rest) # 0 [1,2,3] 4
增量赋值:
增量运算符+,*等实际上是调用__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:2256630301640 str1:2256630239736 str:2256630301640 str1:2256630606152
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)
运行结果:
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']] [['_', '_', '_'], ['_', '_', 'x'], ['_', '_', '_']]
输出三个列表的ID
board = [['_'] * 3 for i in range(3)] print(board) board[1][2] = 'x' print(board) # 输出3个列表的ID print(id(board[0])) print(id(board[1])) print(id(board[2]))
结果是分别属于不一样的ID:
3221578302664 3221578302536 3221578302600
咱们再来看下另一种用法。下面的代码对一个包含3个列表的列表进行*3的操做。
board = [['_'] * 3] * 3 print(board) board[1][2] = 'x' print(board) print(id(board[0])) print(id(board[1])) print(id(board[2]))
运行结果是:
发现id都同样。说明了所有指向的是同一个对象。
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']] [['_', '_', 'x'], ['_', '_', 'x'], ['_', '_', 'x']] 1195278432328 1195278432328 1195278432328
若是对不可变对象中的可变对象进行赋值会产生什么后果,好比下面的这段代码
t = (1, 2, [30, 40]) t[2] += [50, 60] print(t)
运行结果直接报错:
TypeError: 'tuple' object does not support item assignment
咱们能够把代码放在python在线调式网站进行调试
把代码稍微修改下而后再看看状况
t = (1, 2, [30, 40]) t[2].append([50, 60]) print(t)
为何这两种实现会带来不一样的结果呢?
缘由在于t是一个元组属于不可变对象。但用t[2]+=[50,60]的时候是对一个元组进行赋值。
因此报错误。
可是同时t[2]又属于一个列表可变对象。所以数据也更新成功了
可是若是用t[2].append([50,60])的操做则是对一个列表进行操做,而并无对一个元组进行赋值。所以可以更新成功且不会报错误。
这是一个颇有趣的例子。
对于理解可变对象和不可变对象的操做颇有帮助。
查看背后的字节码状况:
import dis a = (1, 2, 3) byte_info = dis.dis('a[2]+=1') print(byte_info)
打印出反编译的结果(反编译后的代码与汇编语言接近)是:
1 0 LOAD_NAME 0 (a) 2 LOAD_CONST 0 (2) 4 DUP_TOP_TWO 6 BINARY_SUBSCR 8 LOAD_CONST 1 (1) 10 INPLACE_ADD 12 ROT_THREE 14 STORE_SUBSCR 16 LOAD_CONST 2 (None) 18 RETURN_VALUE
插播一个有趣的例子
python之交换变量值
import dis def swap1(): x = 5 y = 6 x, y = y,x def swap2(): x = 5 y = 6 tmp = x x = y y = tmp if __name__ == "__main__": print ("***SWAP1***") print (dis.dis(swap1)) print ("***SWAP2***") print (dis.dis(swap2))
查看结果:
*** SWAP1*** 5 0 LOAD_CONST 1 (5) 3 STORE_FAST 0 (x) 6 6 LOAD_CONST 2 (6) 9 STORE_FAST 1 (y) 7 12 LOAD_FAST 1 (y) 15 LOAD_FAST 0 (x) 18 ROT_TWO 19 STORE_FAST 0 (x) 22 STORE_FAST 1 (y) 25 LOAD_CONST 0 (None) 28 RETURN_VALUE ***SWAP2*** 10 0 LOAD_CONST 1 (5) 3 STORE_FAST 0 (x) 11 6 LOAD_CONST 2 (6) 9 STORE_FAST 1 (y) 12 12 LOAD_FAST 0 (x) 15 STORE_FAST 2 (tmp) 13 18 LOAD_FAST 1 (y) 21 STORE_FAST 0 (x) 14 24 LOAD_FAST 2 (tmp) 27 STORE_FAST 1 (y) 30 LOAD_CONST 0 (None) 33 RETURN_VALUE
获得结论
经过字节码能够看到,swap1和swap2最大的区别在于,swap1中经过ROT_TWO交换栈顶的两个元素实现x和y值的互换,swap2中引入了tmp变量,多了一次LOAD_FAST, STORE_FAST的操做。执行一个ROT_TWO指令比执行一个LOAD_FAST+STORE_FAST的指令快,
这也是为何swap1比swap2性能更好的缘由。
在列表和元组中,存放的是具体的对象,如整数对象,字符对象。
以下面的整数b。占据28个字节。由于存放的是整数对象,而非整数自己。
import sys b = 1 print(sys.getsizeof(b)) # 28
对于存放大量数据来讲。咱们选择用数组的形式要好不少。
由于数组存储的不是对象,而是数字的机器翻译。也就是字节表述。
在array中须要规定各个字符的类型,如上表中的Type code。
定义好类型后则数组内的元素必须全是这个类型,不然会报错。
就像下面的代码。类型规定的是b也就是单字节的整数。
可是在插入的时候倒是c字符,则报错:TypeError: an integer is required
import array num = array.array('b') num.append('c') print(num)
在上表中,每一个类型都有字节大小的限制。若是超出了字节大小的限制也是会报错的
仍是b的这个类型,是有符号的单字节整数,那么范围是-128到127.
若是咱们插入128.则报错:signed char is greater than maximum
提示超过了最大
import array num = array.array('b') num.append(128) print(num)
对于数组这种结构体来讲,因为占用的内存小,所以在读取和写入文件的时候的速度更快,相比于列表来讲的话。
下面来对比下:
首先是用列表生成并写入txt文档的用法
import time import struct # struct 例子 # a = 20 # 'i'表示一个int # # struct.pack把python值转化成字节流 # data = struct.pack('i', a) # print(len(data)) # print(repr(data)) # print(data) # 拆包 # a1 = struct.unpack('i', data) # print("a1=", a1) def arry_try_list(): floats = [float for float in range(10**7)] fp = open('list.bin', 'wb') start = time.clock() for f in floats: strdata = struct.pack('i', f) fp.write(strdata) fp.close() end = time.clock() print(end - start) arry_try_list()
执行结果:5.8789385
再看数组的形式:
from array import array from random import random import time 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) array_try()
执行结果: 0.045192899999999994
能够看到速度明显提高了不少
再来对比读文件的速度
from array import array import time def array_try(): floats = array('d') start = time.clock() fp = open('floats.bin', 'rb') # 比直接从文本文件里面读快,后者使用内置的float方法把每一行文字变成浮点数 floats.fromfile(fp, 10**7) fp.close() end = time.clock() print(end - start) array_try()
执行结果是:0.1172238
精准地修改了一个数组的某个字节
#memoryview内存视图 #在不复制内容的状况下操做同一个数组的不一样切片 from array import array #5个短整型有符号整数的数组,类型码是h numbers = array('h', [-2, -1, 0, 1, 2]) memv = memoryview(numbers) print(len(memv)) #转成B类型,无符号字符 memv_oct = memv.cast('B') tolist = memv_oct.tolist() print(tolist) memv_oct[5] = 4 print(numbers)
import bisect # 以空格做为分隔打印S中全部元素再换行. def print_all(S): for x in S: print(x, end = " ") print("") # 有序向量SV. SV = [1, 3, 6, 6, 8, 9] #查找元素. key = int(input()) print(bisect.bisect_left(SV, key)) print(bisect.bisect_right(SV, key)) # 插入新元素. key = 0 bisect.insort_right(SV, key) # 删除重复元素的最后一个. 思考: 如何删除第一个? key = 6 i = bisect.bisect_right(SV, key) i -= 1 # 注意此时i < len(SV)必然成立. 若是确实有key这个元素则删除. if (i > 0 and SV[i] == key): del(SV[i]) print_all(SV) # 删除重复key所在区间: [bisect.bisect_left(SV, key):bisect.bisect_right(SV, key)). del SV[bisect.bisect_left(SV, key):bisect.bisect_right(SV, key)] print_all(SV) # 无序向量USV. USV = [9, 6, 1, 3, 8, 6] # 插入新元素 key = 0 USV.append(key) # 删除重复元素的最后一个. key = 6 # 逆向遍历, 若是存在则删除. i = len(USV) - 1 while i > 0: if USV[i] == key: USV[i] = USV[-1] USV.pop() break i -= 1 print_all(USV) # 删除重复元素的第一个. try: i = USV.index(key) except: pass else: USV[i] = USV[-1] USV.pop() print_all(USV)
pickle模块是将python值转成成byte
try: import cPickle as pickle except: import pickle data = [{'a': 'A', 'b': 2, 'c': 3.0}] # 编码 data_string = pickle.dumps(data) print("DATA:", data) print("PICKLE:", data_string) print(type(data_string)) # 解码 data_from_string = pickle.loads(data_string) print(data_from_string)
双向队列deque
import timeit from collections import deque def way1(): mylist = list(range(100000)) for i in range(100000): mylist.pop() def way2(): mydeque = deque(range(100000)) for i in range(100000): mydeque.pop() if __name__ == "__main__": t1 = timeit.timeit("way1", setup="from __main__ import way1", number=10) print(t1) t2 = timeit.timeit("way2", setup="from __main__ import way2", number=10) print(t2)
结果:
6e-07 1.0000000000001327e-06
deque是双向队列,若是你的业务逻辑里面须要大量的从队列的头或者尾部删除,添加,用deque的性能会大幅提升!若是只是小队列,而且对元素须要随机访问操做,那么list会快一些。
# 双向队列用法 from collections import deque # 建立一个队列 q = deque([1]) print(q) # 往队列中添加一个元素 q.append(2) print(q) # 往队列最左边添加一个元素 q.appendleft(3) print(q) # 同时入队多个元素 q.extend([4, 5, 6]) print(q) # 在最左边同时入队多个元素 q.extendleft([7, 8, 9]) print(q) # 剔除队列中最后一个 q.pop() print(q) # 删除队列最左边的一个元素 q.popleft() print(q) # 清空队列 # q.clear() # 获取队列长度 # length = q.maxlen # print(length)