Python基础教程2--列表、元组和字典
本文主要介绍Python中的列表、元组和字典相关知识,以及如何通过这些数据结构将单个数据组织在一起。
这三个也是在编码过程中最常用的数据结构。
列表list
列表可以称为Python的"劳模",基本上所有的代码中都会用到,列表是可变的,列表的内容可以被修改。
第一篇文章中,序列部分介绍的内容对于列表全部适用。
创建列表
列表主要有两种构造方法:
- 最简单也是最常用的方式就是直接用方括号[]定义
>>> list1 = [] # 空列表 >>> list2 = [1, 2, 3] # 有1,2,3三个初始值的列表
- 通过list([iterable])函数生成列表,此方法常用于将filter/map等函数返回的iterable可迭代对象转换为list对象
>>> list1 = list() # [] >>> list3 = list([1, 2, 3]) # [1, 2, 3] >>> list2 = list('Hello') # ['H', 'e', 'l', 'l', 'o'] >>> list(filter(lambda x: x%2==0, range(0,10))) # filter返回结果是一个filter类对象,通过list()函数返回列表对象使用 [0, 2, 4, 6, 8]
列表方法
以下方法很多都可以通过分片赋值来实现,但是代码可读性不如使用方法
- append(value_item): 在列表末尾追加新对象。
>>> n_list = [1, 2] >>> n_list.append(3) # 直接修改原列表,返回值为空,对应的分片赋值实现为:n_list[len(n_list):] = [3] >>> n_list [1, 2, 3]
- extend(list_item): 在列表末尾一次性追加另外一个序列的多个值。
>>> n_list = [1, 2] >>> n_list.extend([3, 4]) # 直接扩展原列表,返回值为空,对应的分片赋值实现为:n_list[len(n_list):] = [3, 4] >>> n_list [1, 2, 3, 4]
extend和连接操作的主要区别是: extend操作扩展原列表,而连接操作会返回一个全新的列表。
>>> list1 = [1, 2] >>> list2 = [3, 4] >>> list1 + list2 [1, 2, 3, 4] # 生成新的里表 >>> list1 # 原列表不变 [1, 2]
- count(value_item): 统计某个元素在列表中的出现次数,如果是多维列表,只统计出现在第一维的次数。
>>> list1 = [1, 2, 3, 3, 6] >>> list1.count(1) # 1在list1中出现了1次 1 >>> list1.count(3) # 1在list1中出现了2次 2 >>> list2 = [[1, 2], 1, 3, [1, 3, [1, 2]]] >>> list2.count(1) # 1在list1中出现了1次 1 >>> list2.count([1, 2]) 1
- index(value, start ,stop): 从列表中找出某个值第一个匹配项的索引位置,可以指定起始和结束位置(类似切片表示法),默认从列表的开始位置开始查找,直到结尾。
如果未找到匹配值,会引发异常,一般会先通过in判断是否存在,再调用index方法获取位置。
>>> n_list = [11, 22, 33, 22, 66] >>> n_list.index(22) # 第一个匹配项的索引 1 >>> n_list.index(22, 2) # 从第2个索引处(包含2)开始查找,找到的第一个匹配项的索引 3 >>> n_list.index(66) ValueError: 66 is not in list
- insert(index ,value): 将对象插入到列表中的指定索引之前。
>>> n_list = [11, 22, 44, 55] >>> n_list.insert(2, 33) # 在位置2(44)前插入33,直接扩展原列表,返回值为空,对应的分片赋值实现为:n_list[2:2] = [33] >>> n_list [11, 22, 33, 44, 55]
- pop(index): 移除列表中指定索引处的一个元素(默认是最后一个)。
pop方法是列表中唯一一个既修改列表又返回元素值的方法。
>>> n_list = [11, 22, 33, 44, 55] >>> n_list.pop() # 移除最后一个元素,并将元素返回 55 >>> n_list [11, 22, 33, 44] >>> n_list.pop(1) # 移除索引为1的元素(22),并将元素返回 22
通过append和pop方法可以实现两种常用的数据结构-栈(后进先出LIFO)和队列(先进先出FIFO)。
- remove(value): 移除列表中某个值的第一个匹配项。
类似于index,如果值在列表中不存在会引发异常,可以先通过in进行判断,再调用remove方法。
>>> n_list = [11, 22, 33, 22, 66] >>> n_list.remove(22) # 从列表中移除22,返回值为空 >>> n_list [11, 33, 22, 66] # 可以看到只有第一个匹配项被移除了,另外一个22仍然存在于列表中 >>> n_list.remove(55) # 因为55在列表中不存在,引发异常 ValueError: list.remove(x): x not in list
- reverse: 将列表中的元素反向存放。
>>> n_list = [1, 2, 3] >>> n_list.reverse() # 返回值为空 >>> n_list # 修改原列表 [3, 2, 1]
如果不想修改原列表,而是生成一个新的反向列表,可以通过先分片复制再调用reverse方法,或者list(reversed(n_list))方法来实现。
>>> n_list = [1, 2, 3] >>> list1 = n_list[:] # 复制列表 >>> list1.reverse() # 将新列表的元素反向 >>> list1 [3, 2, 1] >>> list2 = list(reversed(n_list)) # reversed方法会返回一个反向的迭代器对象,再调用list函数即可生成新的列表 >>> list2 [3, 2, 1]
列表排序
- sort(key=None, reverse=False): 用于在原位置对列表进行排序,会改变原列表,而不是返回一个排序后的副本。
>>> n_list = [6, 3, 2, 5, 4, 1] >>> result = n_list.sort() >>> print(result) # 方法返回值为空 None >>> n_list [1, 2, 3, 4, 5, 6] # 原列表发生改变
如果想保持原列表不变,只想获取一个排序以后的列表副本,有两个实现方式
- 通过分片生成一个列表副本,再通过sort方法对副本列表进行排序
>>> n_list = [6, 3, 2, 5, 4, 1] >>> c_list = n_list[:] # 生成列表副本 >>> c_list.sort() # 对副本进行排序 >>> c_list [1, 2, 3, 4, 5, 6] # 原列表发生改变
- 使用sorted(iterable, *, key=None, reverse=False)函数,sorted函数返回结果为排序以后的列表副本,而不改变原列表。
>>> n_list = [6, 3, 2, 5, 4, 1] >>> c_list = sorted(n_list) # 生成排序以后的列表副本 >>> c_list [1, 2, 3, 4, 5, 6] >>> n_list [6, 3, 2, 5, 4, 1] # 原列表不变
- 通过分片生成一个列表副本,再通过sort方法对副本列表进行排序
- 高级排序
- 反向排序
sort方法排序以后的原列表或者sorted生成的排序以后的列表副本,都是从小到大排列的, 如果想得到从大到小结果,可以通过将reverse参数设置为True,默认为False。
>>> n_list = [6, 3, 2, 5, 4, 1] >>> n_list.sort(reverse = True) # 调用排序方法,并传入reverse参数 >>> n_list [6, 5, 4, 3, 2, 1] # 结果反向排列
- 自定义比较函数
sort默认排序结果很可能不是我们希望得到的结果,比如以下对于一个IP地址列表的排序
>>> ip_list = ['192.168.10.200', '192.168.9.210', '192.168.9.230', '192.168.10.60'] >>> ip_list.sort() >>> ip_list ['192.168.10.200', '192.168.10.60', '192.168.9.210', '192.168.9.230']
而我们希望的结果是
['192.168.9.210', '192.168.9.230' '192.168.10.60', '192.168.10.200']
在Python2中sort方法可以通过cmp参数自定义比较函数cmp,但是Python3中移除了cmp参数。
自定义比较函数可以通过key参数定义,请注意,参数key是一个在排序过程中使用的函数,但是该函数并不是直接用来确定对象的大小,而是为每个元素创建一个用于比较的键,该函数也只有一个参数。
使用key自定义比较函数主要有两种实现方法
- key函数作为转换器,将元素转换成比较大小的值,比如将IP地址转换成数字返回,然后根据返回的值进行排序。
def key_converter(ip): ip1s = ip.split('.') int_value = int(ip1s[0]) * 0x1000000 + int(ip1s[1]) * 0x10000 + int(ip1s[2]) * 0x100 + int(ip1s[3]) * 1 return int_value ip_list = ['192.168.10.200', '192.168.9.210', '192.168.9.230', '192.168.10.60'] ip_list.sort(key=key_converter) print(ip_list) #['192.168.9.210', '192.168.9.230', '192.168.10.60', '192.168.10.200']
- 定义cmp(a,b)比较函数,参数为两个待比较的值,通过functools模块的cmp_to_key方法生成key参数。
(该函数主要用作从Python 2转换而来的程序的过渡工具)
import functools def key_cmp(ip1, ip2): ip1s = ip1.split('.') ip2s = ip2.split('.') ip1v = int(ip1s[0]) * 0x1000000 + int(ip1s[1]) * 0x10000 + int(ip1s[2]) * 0x100 + int(ip1s[3]) * 1 ip2v = int(ip2s[0]) * 0x1000000 + int(ip2s[1]) * 0x10000 + int(ip2s[2]) * 0x100 + int(ip2s[3]) * 1 if ip1v < ip2v: return -1 if ip1v > ip2v: return 1 return 0 ip_list = ['192.168.10.200', '192.168.9.210', '192.168.9.230', '192.168.10.60'] ip_list.sort(key=functools.cmp_to_key(key_cmp)) print(ip_list) #['192.168.9.210', '192.168.9.230', '192.168.10.60', '192.168.10.200']
- key函数作为转换器,将元素转换成比较大小的值,比如将IP地址转换成数字返回,然后根据返回的值进行排序。
- 反向排序
元组tuple
元组与列表一样,也是一种序列。唯一的不同是元组不能修改。 (字符串也不能修改,后续章节会做相关介绍)
创建元组
元组主要有三种构造方法:
- 最简单也是最常用的方式就是直接用圆括号()定义
>>> tuple1 = ('success', 200) >>> tuple1 ('success', 200) >>> tuple2 = ('success',) # 请注意,如果只有一个值,也必须加逗号,否则就变成了字符串,('success')表示一个字符串,而不是元组 >>> tuple2 ('success',)
- 省略圆括号,直接用逗号隔开
>>> tuple1 = 'success', 200 >>> tuple1 ('success', 200) >>> tuple2 = 'success', # 请注意,如果只有一个值,也必须加逗号,否则就变成了字符串,而不是元组 >>> tuple2 ('success',) >>> ('success', 200,) == ('success', 200) # 如果有两个及以上的元素,最后一个逗号不起作用 True
- 通过tuple()函数生成元组
>>> tuple1 = tuple(['success', 200]) >>> tuple1 ('success', 200) >>> tuple2 = tuple('abc') >>> tuple2 ('a', 'b', 'c') >>> tuple3 = tuple((1, 2, 3)) >>> tuple3 (1, 2, 3)
存在意义
大多数情况,元组都可以用列表来代替,除了以下两种情况
- 元组是不可变的,所以可以作为字典的键值,而列表不能。
- 元组是很多内建函数的返回值。
字典dict
字典是一种通过名字来引用值的映射数据结构,字典中的值没有顺序,值都存储在一个特定的键下,这个键可以是数字、字符串或是元组(不可变)。
可以把字典想象成类似于书本目录的结构,可以根据目录快速的找出要看的章节页,然后快速翻到对应页。
与列表的区别
与列表的主要区别如下:
- 键类型: 列表是通过数字位置索引顺序存储的元素,而字典是通过名字索引(数字、字符串或是元组等不可变类型)来引用的值。
- 顺序: 列表中的元素是有序的,而字典是无序的。
- 自动添加: 列表不能操作超出列表长度的元素,否则会报错,而字典可以随意增/删元素。
- 成员资格: 表达式 k in items, 字典查找的是键,而列表查找的是值
有些场景可能字典比列表更加适用,比如:
- 电话簿: 通过姓名可以很容易的查找到对应的电话号码。
- 书本目录: 通过章节目录可以很容易的找到对应的页数。
创建字典
字典由多个键及其对应的值构成的键-值对(或称为项)组成,键和值之间用冒号(:)隔开,项之间用逗号(,)隔开。
字典主要有两种构造方法:
- 最简单也是最常用的方式就是直接用大括号{}定义
ph_dict = { 'zht': '10000', # 'zht'是键,'10000'是值 'lz': '10086' }
- 也可以使用dict函数创建(此种方式较少使用)
ph_dict = dict(zht='10000', lz='10086')
字典基本操作
- d[k] --获取关联到键k上的值
- d[k] = v --将值v关联到键k上,项可自动添加,如果键不存在则为它赋值,如果存在则替换
- del d[k] --删除键为k的项,如果k在d中不存在,会引发KeyError异常,可以使用d.pop(k, None)方法来删除键值
- k in d --检查d中是否含有键为k的项,True/False
- k not in d --检查d中是否不包含键为k的项,True/False
- len(d) --返回d中项(键-值)数量
字典方法
- get(key[, default]): 获取关联到键k上的值,相对于d[k],get方法更宽松,如果获取不存在的键上的值,d[k]会引发KeyError异常,而get方法不会。
如果使用过其他编程语言,可能习惯使用obj.key的方式访问属性,这种方式在Python中是不支持的,这可能是初学者经常会犯的错误。
获取字典键值的操作,建议都通过get方法获取。
另外还提供了一个default的参数,用于返回当键不存在时的默认值
>>> d = {'name': 'zht', 'age': 18} >>> d.get('name') # 返回键name对应的值 'zht' >>> d['city'] # d[k]获取不存在的键的值,会引发异常 KeyError: 'city' >>> d.get('city') # get方法不会引发异常,而是得到了None值 None >>> d.get('city','Shanghai') # 如果键不存在,返回默认值'Shanghai' 'Shanghai' >>> d.get('age','N/A') # 如果键存在,默认值不起作用,返回的是键对应的值 18
- clear: 清除原字典中所有的项
>>> d = {'name': 'zht', 'age': 18} >>> d.clear() # 操作原字典,无返回值 >>> d {}
- copy: 返回一个具有相同键-值对的新字典
>>> d = {'name': 'zht', 'age': 18} >>> d1 = d.copy() >>> d1 {'name': 'zht', 'age': 18}
请注意,copy是浅复制,列表、字典或对象实例等属于引用值类型, 变量中保存的实际上只是一个指针,这个指针指向内存堆中实际的值,多个值指向同一个内存,如果修改了某个字典的值,其它字典也会改变。
>>> d = {'name': 'zht', 'age': 18, 'address': {'country': 'China', 'city': 'Shandong'}} >>> d1 = d.copy() >>> d1['address']['city'] = 'Shanghai' # 修改字典d1的address中的city值 >>> d1 {'name': 'zht', 'age': 18, 'address': {'country': 'China', 'city': 'Shanghai'}} # 字典d1的值发生了变化 >>> d {'name': 'zht', 'age': 18, 'address': {'country': 'China', 'city': 'Shanghai'}} # 字典d的值也发生了变化
可以通过copy模块的深复制函数deepcopy来避免这种相互影响
>>> from copy import deepcopy >>> d = {'name': 'zht', 'age': 18, 'address': {'country': 'China', 'city': 'Shandong'}} >>> d1 = deepcopy(d) >>> d1['address']['city'] = 'Shanghai' # 修改字典d1的address中的city值 >>> d1 {'name': 'zht', 'age': 18, 'address': {'country': 'China', 'city': 'Shanghai'}} # 字典d1的值发生了变化 >>> d {'name': 'zht', 'age': 18, 'address': {'country': 'China', 'city': 'Shandong'}} # 字典d的值保持不变
- keys(): 获取字典中的键的一个视图对象,当字典更新时这个视图对象会自动更新
>>> d = {'name': 'zht', 'age': 18} >>> keys = d.keys() # 返回键的视图对象 >>> keys dict_keys(['name', 'age']) >>> len(keys) # len函数获取字典中键的数量 2 >>> d['city'] = 'Shanghai' >>> keys dict_keys(['name', 'age', 'city']) # 字典更新时,视图对象会自动更新 >>> key_list = list(keys) # 可以通过list函数将视图转为列表使用,这个列表不会随着字典键值变化而变化 >>> key_list ['name', 'age', 'city']
- values(): 获取字典中的值的一个视图对象,当字典更新时这个视图对象会自动更新
>>> d = {'name': 'zht', 'age': 18} >>> values = d.values() # 返回值的视图对象 >>> values dict_values(['zht', 18]) >>> len(values) # len函数获取字典中值的数量 2 >>> d['city'] = 'Shanghai' >>> values dict_values(['zht', 18, 'Shanghai']) # 字典更新时,视图对象会自动更新
- items(): 获取字典项(键-值)的一个视图对象,当字典更新时这个视图对象会自动更新
>>> d = {'name': 'zht', 'age': 18} >>> items = d.items() # 返回字典项的视图对象 >>> items dict_items([('name', 'zht'), ('age', 18)]) >>> len(items) # len函数获取字典项的数量 2 >>> d['city'] = 'Shanghai' >>> items dict_items([('name', 'zht'), ('age', 18), ('city', 'Shanghai')]) # 字典更新时,视图对象会自动更新
- setdefault(key[, default]): 设置字典中不存在的键的默认值,如果键已经存在,则setdefault不起作用。
>>> d = {'name':'zht'} >>> d.setdefault('name', 'N/A') # 如果键存在,不起作用,返回键对应的值 'zht' >>> d # 字典没变化 {'name': 'zht'} >>> d.setdefault('city', 'N/A') # 设置默认值,并返回默认值 'N/A' >>> d # 字典被更新 {'name': 'zht', 'city': 'N/A'} >>> d.get('city') # 键未设置,返回默认值 'N/A' >>> d.get('city','None') # 键未设置时,如果setdefault了,get方法的默认值不起作用 'N/A' >>> d['city'] = 'Shanghai' # 设置键值 >>> d.get('city') 'Shanghai'
setdefault方法和d[k]=v表达式的主要区别时,当键存在时setdefault不起作用,而d[k]=v会更新字典。
- update([other_dict]): 利用一个字典更新原字典,提供的字典中的项会被添加到原字典中,如果键相同则会覆盖。
>>> d = {'name': 'zht', 'city': 'Shandong'} >>> d.update({'age' : 18, 'city':'Shanghai'}) # age键值项会添加到字典d中,用新字典项中city对应的值,覆盖原字典项中city对应的值 >>>d {'name': 'zht', 'city': 'Shanghai', 'age': 18}
- pop(key[, default]): 获取给定键对应的值,然后将这个键值对从字典中移除。
>>> d = {'name': 'zht', 'city': 'Shanghai'} >>> d.pop('city') # 返回键对应的值 'Shanghai' >>>d # 将键值对从字典中移除 {'name': 'zht'} >>> d.pop('age') # 如果键不存在会引发异常 KeyError: 'age' >>> d.pop('age',18) # 如果键不存在,可以提供默认值 18