Python内置数据结构之字典(完整版)

今天主要讲解上次未完成的内置数据结构-字典。小白这几天比较忙,忙的忘记了健身及写作,特发此文以作补偿。

Python字典简介

Python内置了字典:dict的支持,dict全称dictionary,在其他语言中也称为map,使用键-值(key-value)存储,具有极快的查找速度。

这种key-value存储方式,在放进去的时候,必须根据key算出value的存放位置,这样,取的时候才能根据key直接拿到value。

请务必注意,dict内部存放的顺序和key放入的顺序是没有关系的。

和list比较,dict有以下几个特点:

  1. 查找和插入的速度极快,不会随着key的增加而增加;
  2. 需要占用大量的内存,内存浪费多。

而list相反:

  1. 查找和插入的时间随着元素的增加而增加;
  2. 占用空间小,浪费内存很少。

所以,dict是用空间来换取时间的一种方法。

字典的定义及初始化,

d = {}
d = dict()
d = {'a': 1, 'b': 2}
d = dict([('a', 1), ('b', 2)]) # 可迭代对象的元素必须是一个二元组,二元组的第0个元素为字典的key,第1个元素为字典的value
d = dict.fromkeys(range(5)) # 传入的可迭代元素为key,值为None
d = dict.fromkeys(range(5), 'abc') # 传入的可迭代元素为key,值为abc

dict可以用在需要高速查找的很多地方,在Python代码中几乎无处不在,正确使用dict非常重要,需要牢记的第一条就是dict的key必须是不可变对象。这是因为dict根据key来计算value的存储位置,如果每次计算相同的key得到的结果不同,那dict内部就完全混乱了。这个通过key计算位置的算法称为哈希算法(Hash)。

要保证hash的正确性,作为key的对象就不能变。在Python中,字符串、整数等都是不可变的,因此,可以放心地作为key。而list是可变的,就不能作为key:

In[1]: d = {}
In[2]: key = [1, 2, 3]
In[3]: d[key] = 'a list'
----------------------------------------------------------------------
TypeError                                Traceback (most recent call last)
TypeError: unhashable type: 'list'

字典常用方法

首先总结一下有哪些常用的方法:

  • 增加:update
  • 删除:pop, popitem, clear
  • 修改:update
  • 查找:get
  • 其他:keys, values, items, fromkeys

字典增加或修改

update方法会修改或增加字典内容。如有下一个字典:

In[12]: dict01 = {'laven': 23, 'taoqi': 20}
In[13]: dict01
Out[13]: {'laven': 23, 'taoqi': 20}
# 使用update增加一个key-value
In[14]: dict01.update(a=123)
In[15]: dict01
Out[15]: {'a': 123, 'laven': 23, 'taoqi': 20}

当然,我们直接使用dict01['a'] = 123也是可以的,只不过我们这里介绍的是字典的update方法。接下来看看update方法的修改作用:

In[15]: dict01
Out[15]: {'a': 123, 'laven': 23, 'taoqi': 20}

In[16]: dict01.update(laven=21)

In[17]: dict01

Out[17]: {'a': 123, 'laven': 21, 'taoqi': 20}

update方法也可以接收一个二元组列表作为其参数来增加字典:

In[17]: dict01
Out[17]: {'a': 123, 'laven': 21, 'taoqi': 20}

In[18]: dict01.update([('b', 456), ('c', 789)])

In[19]: dict01

Out[19]: {'a': 123, 'b': 456, 'c': 789, 'laven': 21, 'taoqi': 20}

update的参数也可以是一个字典,不过这种形式用的比较少,还是举个例子吧:

In[20]: dict01
Out[20]: {'a': 123, 'b': 456, 'c': 789, 'laven': 21, 'taoqi': 20}
In[21]: dict01.update({'d': 987, 'e': 654})
In[22]: dict01
Out[22]: {'a': 123, 'b': 456, 'c': 789, 'd': 987, 'e': 654, 'laven': 21, 'taoqi': 20}

总结一下update的用法,它的参数的几种情况:

  • 可以是字典
  • 可以是由二元组构成的可迭代对象
  • 关键字参数

字典删除

删除字典有三种形式:

  1. pop 删除指定的key,返回该key的value
  2. popitem 随机删除,返回随机删除的一个kv二元组
  3. clear 清空字典

接下来看例子:

In[23]: dict01 = {'laven': 23, 'taoqi': 20}
# pop一个存在的key
In[24]: dict01.pop('laven')
Out[24]: 23
# pop一个不存在的key呢?

In[25]: dict01.pop('lavenliu')
---------------------------------------------------------------------------

KeyError                                  Traceback (most recent call last)<ipython-input-25-a346227feeaa> in <module>()----> 1 dict01.pop('lavenliu')
KeyError: 'lavenliu'

如果pop一个不存在的key,为了不让出现KeyError异常,我们可以为其设置一个默认值,如下:

In[26]: dict01.pop('lavenliu', 'not exist')
Out[26]: 'not exist'

接下来看看popitem方法:

In[27]: dict01

Out[27]: {'taoqi': 20}
In[28]: dict01['laven'] = 23
In[29]: dict01
Out[29]: {'laven': 23, 'taoqi': 20}
In[30]: dict01.popitem()
Out[30]: ('laven', 23) # 随机返回一个二元组
In[31]: dict01
Out[31]: {'taoqi': 20}

如果是空字典呢?我们还能否进行popitem方法呢?大家可以试试看。

最后看看字典的clear方法:

In[34]: dict01 = {'laven': 23, 'taoqi': 20}
In[35]: dict01.clear()
In[36]: dict01
Out[36]: {}

如果空字典再次执行clear方法会怎样呢?大家可以试试看。

字典查找

字典查找也可以叫做字典的访问,如果我们知道字典有哪些key,直接进行访问就可以了。如下示例:

In[38]: dict01 = {'laven': 23, 'taoqi': 20}
In[39]: dict01['taoqi']
Out[39]: 20
In[40]: dict01['laven']
Out[40]: 23

我们使用get方法试试呢?

In[41]: dict01.get('laven')
Out[41]: 23
# 访问一个不存在的key呢
In[42]: dict01.get('lavenliu')# 发现什么都没有返回,我们可以给get方法一个默认值,
In[46]: dict01.get('lavenliu', -1)
Out[46]: -1
# 当对存在的key进行get时,返回的还是相应的value
In[47]: dict01.get('laven', -1)
Out[47]: 23

字典其他方法

字典的其他方法:

  • keys 返回所有的key
  • values 返回所有的value
  • items 返回一个二元组列表
  • fromkeys 可以批量创建字典的key-value

下面分别演示:

# keys方法演示
In[50]: dict01 = {'laven': 23, 'taoqi': 20}
In[51]: dict01.keys()
Out[51]: dict_keys(['laven', 'taoqi'])

# values方法演示
In[52]: dict01.values()
Out[52]: dict_values([23, 20])

# items方法演示
In[53]: dict01.items()
Out[53]: dict_items([('laven', 23), ('taoqi', 20)])

# fromkeys方法演示
In[54]: dict02 = {}
In[55]: dict02 = dict.fromkeys(range(5)) # 传入的可迭代元素为keys
In[56]: dict02
Out[56]: {0: None, 1: None, 2: None, 3: None, 4: None}
In[57]: dict02 = dict.fromkeys(range(5), 'not none') # 还可以给一个默认值
In[58]: dict02
Out[58]: {0: 'not none', 1: 'not none', 2: 'not none', 3: 'not none', 4: 'not none'}

字典的遍历

字典的遍历很简单:

In[48]: dict01
Out[48]: {'laven': 23, 'taoqi': 20}
In[49]: for k, v in dict01.items():
    ...:     print(k, '=>', v)
    ...:     
laven => 23
taoqi => 20

In[63]: for k in dict01.keys():
    ...:     print(k, "=>", dict01[k])
    ...:     
laven => 23
taoqi => 20

keysvaluesitems返回的都是生成器,它并不会复制一份内存。而Python2对应的方法返回的是列表,会复制一份。

还有一个不常用的方法叫做enumerate,它返回的是key的所以及相应的key。示例如下:

In[64]: for idx, k in enumerate(dict01):
    ...:     print(idx, "=>", k)
    ...:     
0 => laven
1 => taoqi

# 还可以这样使用
In[65]: for i, (k, v) in enumerate(dict01.items()):
    ...:     print(i, k, "=>", v)
    ...:     
0 laven => 23
1 taoqi => 20

根据value找其对应的key:

In[88]: d = {'a': 1, 'b': 2, 'c': 3}
In[89]: d.update(c=123)
In[90]: d
Out[90]: {'a': 1, 'b': 2, 'c': 123}
In[91]: for k, v in d.items():
    ...:     if v == 123:
    ...:         print(k)
    ...:         break
    ...:     
c

字典排序

由于字典是散列表,没有顺序,适合插入、查询等操作。另外字典的key不一定是字符串,但一定是不可变对象。

字典排序我们使用内置的sorted函数:

In[107]: dict01
Out[107]: {'laven': 23, 'taoqi': 20}

In[108]: sorted(dict01.items(), key=lambda d: d[1], reverse=True)
Out[108]: [('laven', 23), ('taoqi', 20)]

In[109]: sorted(dict01.items(), key=lambda d: d[1], reverse=False)
Out[109]: [('taoqi', 20), ('laven', 23)]

默认字典

default初始化的时候,需要传入一个函数,这个函数也叫工厂函数。当我们使用下标访问一个key的时候,如果这个key不存在,defaultdict会自动调用初始化时传入的函数,生成一个对象作为这个key的value。一个默认字典的例子,我们来构造一个多值的字典:

In[79]: from collections import defaultdict
# 常规方法
In[80]: d = {}
In[81]: for k in range(10):
    ...:     for v in range(10):
    ...:         if k not in d.keys():
    ...:             d[k] = []
    ...:         d[k].append(v)
    ...:         
In[82]: d
Out[82]: {0: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
          1: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
          2: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
          3: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
          4: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
          5: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
          6: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
          7: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
          8: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
          9: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]}

# 使用默认字典的方法

In[83]: d = defaultdict(list)
In[84]: d
Out[84]: defaultdict(list, {})
In[85]: print(d)defaultdict(<class 'list'>, {})
In[86]: for k in range(10):
    ...:     for v in range(10):
    ...:         d[k].append(v)
    ...:         
In[87]: d
Out[87]: defaultdict(list,
            {0: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
             1: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
             2: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
             3: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
             4: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
             5: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
             6: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
             7: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
             8: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
             9: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]})

有序字典

在绝大多数的编程语言中,字典都是无序的。在Python中,字典也是无序的。但标准库提供了有序字典的库,我们可以创建有序字典。但是有序字典比常规的字典要占用多一倍的内存空间。

示例如下:

In[4]: from collections import OrderedDict

In[5]: od = OrderedDict()

In[6]: od['a'] = 1
In[7]: od['b'] = 2
In[8]: od['c'] = 3
In[9]: od.keys()
Out[9]: odict_keys(['a', 'b', 'c'])
In[10]: for k, v in od.items()
:...:          print(k, '->', v)
...: 
a -> 1
b -> 2
c -> 3

字典的限制

  • 字典的key不能重复;
  • 字典的key需要可hash。

总结

今天,我们讲解了字典的绝大部分的知识点。当然了Python字典的实现这里就不讲解了,可以作为拓展内容自己进行学习了解。这里只是提一下,Python字典的实现主要有:“拉链法”和“开地址法”。

原文发布于微信公众号 - 小白的技术客栈(XBDJSKZ)

原文发表时间:2017-08-22

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏玄魂工作室

如何学python 第10课 创建自己的函数

在上一节课里,我们学习了一些关于错误检测和错误处理的知识。这节课我们来学习函数。我们将会介绍什么是函数,以及如何创建函数。 函数是什么? 函数是一系列指令的集合...

25512
来自专栏烂笔头

Python标准库笔记(2) — re模块

目录[-] re模块提供了一系列功能强大的正则表达式(regular expression)工具,它们允许你快速检查给定字符串是否与给定的模式匹配(matc...

3244
来自专栏Vamei实验室

Python进阶06 循环对象

这一讲的主要目的是为了大家在读Python程序的时候对循环对象有一个基本概念。 循环对象的并不是随着Python的诞生就存在的,但它的发展迅速,特别是Pytho...

1847
来自专栏编程

python中的变量

变量与数据类型 变量 编程语言中为了能够更好的处理数据,都需要使用一些变量。Python 语言的变量可以是各种不同的数据类型,使用变量的时候不需要声明直接使用就...

1680
来自专栏magicsoar

C++内存布局(1)-让new出的两个变量在堆上的地址连续

大家都知道栈的地址按照从高到低的顺序增长的, 而堆的地址是按照从底到高的顺序增长的。 int *n1 = new int(1); int *n2 = new i...

1899
来自专栏我爱编程

Day17内建模块datetime

datetime >>> from datetime import datetime >>> now = datetime.now() >>> print(no...

2675
来自专栏xingoo, 一个梦想做发明家的程序员

c++中类长度解析

通常我们定义一个类,它所占的空间有多大呢? 首先我们看一下下面的这个类 class A{ public: void func1(void){ ...

1675
来自专栏有趣的Python

慕课网-c++远征离港篇-学习笔记const 与引用

c++远征离港篇 离港总动员 引用VS指针、 #define VS const 函数默认值&函数重载 内存管理(头疼) 封装 继承 多态 c++语言引用: 引用...

34410
来自专栏java初学

java中 i = i++和 j = i++ 的区别

36910
来自专栏程序员八阿哥

年薪20万Python工程师进阶(4):一文读懂Python可迭代对象、迭代器和生成器

序列可以迭代的原因:iter 函数。解释器需要迭代对象 x 时,会自动调用 iter(x)。内置的 iter 函数有以下作用:

833

扫码关注云+社区