列表
- 列表
有序、连续地储存每个元素的地址(列表对象的值是元素对象的地址),可以改变储存内容字符串可看作一种列表
列表的创建
基本语法
[]创建list = ['python', 'java', 'object-c']通过函数
list()转换可迭代对象list = list(range(101)) # range对象 list = list("Python") # 字符串对象列表推导式生成list = [x**2 for x in range(10) if x%2==0]
range()函数range([start], end, [step])
- 创建从
start(包含)到end(不包含)且步长为step的包含一系列整数的range类型对象end为必选项,其它参数可选- 必须通过
list()转换为列表,否则仍然是range类型(容易掉坑)
元素的添加
不生成新列表末尾添加
- 单个元素:
[0,1].append(2) - 多个元素:
[0,1].extend([2,3])
- 单个元素:
插入添加
>>> print([0,1,2,3].insert(2,'a')) None ''' 为什么是None,而不是[0,1,'a',2,3]呢? 因为“不生成新的列表”,所以insert()函数的返回值是None,而非新的列表。而print()函数打印的是这个返回值。 这是容易犯的错误,其它类型的函数同理。 ''' # 正确操作如下:先赋值,再调用,最后打印 >>> l = [0,1,2,3] >>> l.insert(2,'a') >>> print(l) # 此时打印的是“list的值”而不是“insert()返回的None” [0, 1, 'a', 2, 3]
生成新列表- 使用
+运算:[0,1]+[2] - 使用
*运算:[0,1]*2
- 使用
补充:为什么末尾添加的效率高,而插入添加的效率低?
因为列表是
连续的,就像一列无限高的书柜,从底部开始放置书籍(元素的地址),每格只能放置一本书。如果想要往某一格中插入书,就必须把格子以上所有的书取出来重新放置。插入是这样子的,后续如果我们不需要某本书(删除),同理操作。
元素的删除
del list[2]:删除第3个元素,效率低list.pop(2):删除第3个元素并返回该元素的值;若留空则删除最末尾的元素,此时效率高list.remove("abc"):删除首次出现、值为"abc"的元素
元素的访问和计数
list = [3,4,5,6]
# 1. 直接通过索引
a = list[0] # 结果为3
# 2. index(value,[start,[end]])获取首次出现的位置
b = list.index(5,1,3) # 结果为2
# 3. count()获取出现的次数
c = list.count(4) # 结果为1
len(list) # 结果为4
2 not in list # 结果为True列表的切片
详见变量的基本类型中字符串部分
复制列表可以使用list[:],获得新的列表
列表的遍历
l = list("I love Python!")
for i in l:
print(i, end=' ')列表的排序
不生成新列表
- 升序(或降序)排序:
list.sort([reverse=True]) 随机顺序:
import random random.shuffle(list)
- 升序(或降序)排序:
生成新列表
- 升序排序:
sorted(list) - 降序排序:
sorted(list,reverse=True)
- 升序排序:
通过迭代器(逆序)
>>> a = list(range(10)) >>> a [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> b = reversed(a) >>> b <list_reverseiterator object at 0x00000242D8C28C70> >>> c = list(b) >>> c [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] >>> d = list(b) >>> d []注意:
reversed()生成的迭代器仅能使用1次
列表的内置函数
l = list(range(10))
max(l)
min(l)
sum(l)多维列表
>>> l = [
... ['P','Y','T','H','O','N'],
... [1,2,3,4,5,6],
... [True,False]
... ]
>>> l[0][1]
'Y'元组
列表可变,元组不可变- 因为
元组不可变,所以访问速度更快- 因为
元组不可变,所以可作为字典的键除此之外两者几乎相等,下文只提及差异
元组的创建
基本语法
()创建,但存在特殊情况:# 特殊情况1:不加括号也行,注意与序列解包赋值区分 t1 = (1,2) t2 = 1,2 # 特殊情况2:单个整型或浮点型需加逗号,否则不能识别为元组 t3 = (1) # <class 'int'> t4 = (1,) # <class 'tuple'>通过函数
tuple()转换可迭代对象t1 = tuple(range(101)) # range对象 t2 = tuple("Python") # 字符串对象生成器推导式生成tuple = tuple((x**2 for x in range(10) if x%2==0)) # 为什么额外加上tuple()呢?
生成器推导式生成
list或tuple# 列表推导式直接生成list l1 = [x for x in range(5)] # <class 'list'> # 生成器推导式间接生成list type((x for x in range(5))) # <class 'generator'> l2 = list((x for x in range(5))) # <class 'list'> # 元组推导式直接生成tuple? t1 = (x for x in range(5)) # <class 'generator'> # 生成器推导式间接生成tuple type((x for x in range(5))) # <class 'generator'> t2 = tuple((x for x in range(5))) # <class 'tuple'> # 生成器推导式的__next__()方法 >>> g = (x for x in range(3)) >>> g.__next__() 0 >>> g.__next__() 1 >>> g.__next__() 2 >>> g.__next__() Traceback (most recent call last): File "<python-input-7>", line 1, in <module> g.__next__() ~~~~~~~~~~^^ StopIteration ''' 总结: 1. list既可以通过列表推导式直接生成,也可以通过生成器推导式间接生成 2. tuple没有元组推导式,只能通过生成器推导式间接生成 3. 需要间接生成list或tuple时,生成器推导式只能使用1次 4. 生成器推导式内置遍历方法__next__()
元素的增删改
因为元组不可变,所以其元素不支持增删改
元组的排序
因为元组不可变,所以无内置的tuple.sort([reverse=True])方法,只能通过sorted()进行排序
字典
三种序列的区别(除字符串、集合外)
- 列表是
有序、可变、连续的序列- 元组是
有序、不可变、连续的序列- 字典是
无序、可变、不连续的序列
字典的定义和本质
- 字典的元素是
键值对(key-value),每一个键值对既包括键对象也包括值对象。 - 字典通过
键对象寻找值对象,类似于列表、元组通过内置索引(如0,1,2…)寻找元素对象。 键对象是不能改变的对象,元组可以作为键对象,而列表不能作为键对象;键对象不能重复。值对象是可以改变或不可以改变的对象,也可以重复。
字典的计算机底层原理
- 组成字典的基本单元是
bucket(存储桶),每个bucket存储一个键对象和一个对应的值对象。键对象空间存放完整的哈希值,值对象空间存放所指向对象的地址。存储字典的键值对
内存划分
- 字典的本质是
散列表,散列表是一个不连续的数组,所以每个bucket可以存储空内容。- 创建字典,一次性向内存请求划分
2**n个bucket所占据的存储空间。且该存储空间是可动态更新的,一旦存放了接近2/3存储空间的bucket,会自动扩容。这种扩容不是在原有数组上操作,而是重新请求划分更大的空间,并重新计算偏移量、复制原有内容到新数组的空间。键对象取哈希值
- 因为基本单元bucket的结构、大小是统一的,所以可以通过
偏移量寻找指定的bucket(例如,每本书厚5cm,从左往右竖着放,可通过“第一本书起始向右偏移10cm”找到第三本书的起始)。- 因为
键对象可以是任一类型,而计算机只能存储二进制整型,所以通过取键对象哈希值、再转换为二进制id=bin(hash(key))的方式,获取键对象唯一的哈希值(类似于身份证)及其后n位(身份证后n位),将哈希值后n位转换为十进制dec(id[-n:])即可得到偏移量。- 根据偏移量找到具体的存储空间,如果bucket没有存储内容(存储空内容),则在键对象存储完整的哈希值,在值对象存储内容的地址(哈希值后n位不用存储)。如果已经有了存储内容,则执行扩容操作,哈希值后n位会更多,直到偏移量对应的bucket没有内容。
总结解释
2.2是键对象必须可散列且不能改变、重复的原因,因为需要靠键对象确定偏移量,否则找不到或者找错对应bucket。- 这种操作是典型的
空间换时间,牺牲巨量的内存,换取极速的查询。- 不要同时修改、遍历字典,有可能会使内存溢出(扩容)、运行缓慢。
查询字典的键值对
- 计算给出键对象的偏移量等过程同理,不同的是即使得到相同的哈希值后n位,计算机一定会再次核对完整的哈希值,防止重复,毕竟身份证后n位也有可能相同。
字典的创建
建议初学者先看后文的zip()函数专题,再从此开始看。本章节较为困难,反复多读几遍。基本语法
{}创建dict_ex = {'a': 100, 'b': 200, 'c': 300}通过函数
dict()转换# 传入关键词参数 dict_ex1 = dict('a'=100,'b'=200,'c'=300) # 传入含元组的序列(含元组的序列可替换为含元组的zip对象) dict_ex2 = dict([('a',100),('b',200),('c',300)])通过函数
zip()创建key = ['a','b','c'] value = [100,200,300] kv = zip(key,value) # <zip object at 0x000001DD93ED1F00> dict_ex = dict(kv) # {'a': 100, 'b': 200, 'c': 300}通过方法
fromkeys()创建空值字典dict_ex = dict.fromkeys(['a','b','c']) print(dict_ex) # {'a': None, 'b': None, 'c': None}
函数
zip()生成list、tuple或dict
zip()将传入的多个列表按位置顺序打包成元组,返回的是zip对象若列表的元素个数不一致,则以最短的列表个数作为索引截止,所有列表的超出该索引的所有元素
直接丢弃a = [1,2,3] b = ['a','b','c','d'] c = [100,200,300,400,500,600] # 生成list zipped = zip(a,b,c) print(zipped) # <zip object at 0x000001DD93EEA340> list_ex = list(zipped) print(list_ex) # [(1, 'a', 100), (2, 'b', 200), (3, 'c', 300)] # 生成tuple zipped = zip(a,b,c) print(zipped) # <zip object at 0x000002882E379A40> tuple_ex = tuple(zipped) print(tuple_ex) # ((1, 'a', 100), (2, 'b', 200), (3, 'c', 300)) # 生成dict见上文
元素的访问和计数
dict_ex = {'a': 100, 'b': 200, 'c': 300}
# 通过key索引访问value
dict_ex['a'] # 100
# 通过get方法访问value
dict_ex.get('c') # 300
dict_ex.get('d','我是缺省值') # '我是缺省值'
# 枚举所有的键和(或)值
dict_ex.items() # dict_items([('a', 100), ('b', 200), ('c', 300)])
dict_ex.keys() # dict_keys(['a', 'b', 'c'])
dict_ex.values() # dict_values([100, 200, 300])
len(dict_ex) # 结果为3
'a' not in dict_ex # 结果为False推荐使用get()方法,使用索引访问若无法找到key会报错,而get()方法不会如此且支持自定义缺省值
元素的增删改
dict_ex = {'a': 100, 'b': 200, 'c': 300}
# 单个元素的增加或覆盖
dict_ex['a'] = 400
dict_ex['d'] = 1000
# 多个元素的增加或覆盖
dict_addons = {'c':400, 'd':500, 'e':600}
dict_ex.update(dict_addons)
# 指定删除k-v
del(dict_ex['a']) # 单个元素,不返回被删除的值
dict_ex.pop('a') # 单个元素,返回被删除的值
dict_ex.clear() # 所有元素
# 随机删除k-v
dict_ex.popitem()
'''
注意,虽然是该方法是随机的,但是本质是删除散列表最后一个储存的k-v,详见“字典的定义和本质”
'''序列解包赋值专题拓展
序列解包赋值,原则上可以用所有广义上的序列
- 变量的序列解包赋值:
a,b = 100, 200 # 变量vs元组 - 序列(列表、元组)的序列解包赋值:
(a,b,c) = "str" # 元组vs字符串 序列(字典)的序列解包赋值
''' 默认对key进行操作 ''' dict_ex = {'a': 100, 'b': 200, 'c': 300} p, q, r = dict_ex print(p) # a p, q, r = dict_ex.keys() print(p) # a p, q, r = dict_ex.values() print(p) # 100
行列表专题拓展
问题:表格如下,请利用序列知识创建行列表,并打印学号。
| 姓名 | 学号 | 班级 |
|---|---|---|
| 卢本伟 | 101 | A1 |
| 羅大佐 | 102 | B1 |
| 蔡徐坤 | 103 | C1 |
'''
示例代码,手动敲一遍
'''
row1 = {'name':'卢本伟','series':101,'class':'A1'}
row2 = {'name':'羅大佐','series':102,'class':'B1'}
row3 = {'name':'蔡徐坤','series':103,'class':'C1'}
table = [row1,row2,row3]
for row in table:
print(row.get('series'),end='\t')集合
- 集合的元素是
字典的键对象,所以不能重复 集合的创建:
# 基础语法方式 set_ex = {0,1,2} # set()函数实现自动删除重复元素 set_ex = set([0,1,1,3,4,2,5]) # {0, 1, 2, 3, 4, 5}- 集合元素的添加:
set_ex.add(4) 集合元素的删除:
# 单个 set_ex.remove(0) # 整个 set_ex.clear()集合元素的操作:
# 并集 a | b a.union(b) # 交集 a & b a.intersection(b) # 差集 a - b a.difference(b)
1 条评论