Python 数据结构之 Dict and Set

Python 基本数据类型,前面已经认识了 Pthon 中的数字(Number)、字符串(String)、元组(Tuple)、列表(List)。接下来来看 Python 中的 字典(Dict)集合(Set)

Python 中的字典(Dict)和集合(Set)容器模型:

字典(Dict)

Python 中另一个非常有用的内置数据类型是字典(Dictionary),相当于其他语言中的 Map(表征映射关系)。

认识字典

Python 字典(dict)是一种 无序的、可变的 数据集,它的元素是以 “键值对(key-value)” 的形式存储,是 Python 中唯一的 映射类型

映射 是数学中的术语,简单理解,它指的是元素之间相互对应的关系,即通过一个元素,可以唯一找到另一个元素。

例如,期中考试小明、小红、小刚分别考了 95、90 和 90 分。结合之前的知识,自然而然,我们想到了使用列表的形式来存放上述数据,我们需要新创建一个列表来专门放分数,而且要保证和姓名的顺序是一致的,这是很麻烦。

字典的出现,为我们提供了一种更好的方式来 表达这种具有映射关系的数据(小明 –> 95,小红 –> 90,小刚 –> 90)。

事实上,字典类型很像我们学生时代常用的新华字典。我们知道,通过新华字典中的音节表,可以快速找到想要查找的汉字。其中,字典里的音节表就相当于字典类型中的键(Key),而键对应的汉字则相当于值(Value)。

Key-Value >>>>

类似于 List && tuple,字典中,习惯将各元素对应的索引称为键(key),各个键对应的元素称为值(value),一起构成了 “key-value” (键值对)。

通过键(Key)来获取相应值(Value)的模式(key-value)要求:

  • key(键)字典中的键必须唯一,且不可变(只能使用数字、字符串或者元组,不能使用列表);
  • value(值)是无序、可变的元素对象,可以是 Python 支持的任意数据类型。

小结:字典(dict)是 Python 中一种无序、可变的,采用 key-value 键值对来表述具有映射关系数据的内置数据类型。


字典的构建

创建字典的方式有很多,下面开始一一介绍:

[1] >>>> 字典的标准创建格式:

字典的标准创建格式很简单,用大括号 { } 来标识。它是一个无序的 key:value 键值对的集合,相邻元素之间使用逗号 , 分隔:

1
dict1 = {key1 : value1, key2 : value2 }

代码示范:

1
2
3
4
5
6
7
# 使用字符串作为key
students_scores = {'小明':95, '小红':90, '小刚':90}
print(students_scores)

# 使用元组和数字作为 key
dict1 = {(20, 30):'great', 30:[1,2,3]}
print(dict1)

可以看到,字典的键可以是整数、字符串或者元组,只要符合唯一和不可变的特性就行;字典的值可以是 Python 支持的任意数据类型。

空字典 >>>>

使用此方式创建字典时,字典中 key-value 可以有多个,也可以一个都没有(空字典),例如:

1
2
3
4
5
>>> emptydict = {}
>>> emptydict
{}
>>> type(emptydict)
<class 'dict'>

[2] >>>> fromkeys() 字典方法创建:

Python dict 中,提供了 fromkeys() 方法来根据 Key 列表创建带有默认值的字典,具体格式为:

1
dictname = dict.fromkeys(list,value=None)

其中,list 参数表示字典中所有键的列表(list);value 参数表示默认值,如果不写,则为空值 None。

例如:

1
2
3
4
>>> knowledge = ['语文', '数学', '英语']
>>> scores = dict.fromkeys(knowledge, 60)
>>> print(scores)
{'语文': 60, '数学': 60, '英语': 60}

这种创建方式通常用于初始化字典,设置 value 的默认值。


[3] >>>> dict() 内建函数创建字典:

语法规则如下:

1
2
3
class dict(**kwarg)	# 传入关键字
class dict(mapping, **kwarg) # 映射函数方式来构造字典
class dict(iterable, **kwarg) # 可迭代对象方式来构造字典

官方是这么解释的:

dict() 方法会返回一个新的字典,并且基于可选的位置参数(mapping 或者 iterable)和可能为空的关键字(Key)参数集来初始化新字典。

  • 如果没有给出位置参数以及关键字参数集,将创建一个空字典;
  • 如果给出一个位置参数并且其属于映射对象(mapping),将创建一个具有与映射对象相同键值对的字典;否则的话,位置参数必须为一个 iterable 对象。 该可迭代对象中的每一项本身必须为一个刚好包含两个元素的可迭代对象。 每一项中的第一个对象将成为新字典的一个键,第二个对象将成为其对应的值。 如果一个键出现一次以上,该键的最后一个值将成为其在新字典中对应的值;
  • 如果给出了关键字参数集(key-vaule),则关键字参数及其值(例如:one=1)会被加入到基于位置参数创建的字典。 如果要加入的键已存在,来自关键字参数的值将替代来自位置参数的值。

详细示例如下(请对照参看):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
>>> # 没有给出位置参数以及关键字参数集
>>> dict1 = dict()
>>> dict1
{}

>>> # 1. 给出位置参数且其属于映射对象(`mapping`)
>>> dict2 = dict(zip(['one', 'two', 'three'], [1, 2, 3]))
>>> dict2
{'one': 1, 'two': 2, 'three': 3}

>>> # 2. 给出位置参数且是一个 `iterable` 对象(List 或者 元组 或者字典)
# List
>>> dict3 = dict([('two',2), ('one',1), ('three',3)])
>>> dict3
{'two': 2, 'one': 1, 'three': 3}
>>> dict4 = dict([['two',2], ['one',1], ['three',3]])
>>> dict4
{'two': 2, 'one': 1, 'three': 3}

# Tuple
>>> dict5 = dict((['two',2], ['one',1], ['three',3]))
>>> dict5
{'two': 2, 'one': 1, 'three': 3}
>>> dict5 = dict((('two',2), ('one',1), ('three',3)))
>>> dict5
{'two': 2, 'one': 1, 'three': 3}

# Dict
>>> dict6 = dict({'three': 3, 'one': 1, 'two': 2})
>>> dict6
{'three': 3, 'one': 1, 'two': 2}

>>> # 3. 给出位置参数(dict)以及关键字参数集(two=2)
>>> dict7 = dict({'one': 1, 'three': 3}, two=2)
>>> dict7
{'one': 1, 'three': 3, 'two': 2}

# 验证字典的无序性
>>> dict1 == dict2 == dict3 == dict4 == dict5 == dict6 == dict7
False

[4] >>>> 推导式方法创建字典:

和列表的推导式方法定义类似,我们同样还可以使用推导式的方法创建字典:

1
2
>>> {x:x**2 for x in (2,4,6,8)}
{2: 4, 4: 16, 6: 36, 8: 64}

字典的删除

和删除列表、元组一样,手动删除字典也可以使用 del 关键字,例如:

1
2
3
4
5
6
7
8
9
>>> a = dict(two=0.65, one=88, three=100, four=-59)
>>> print(a)
{'two': 0.65, 'one': 88, 'three': 100, 'four': -59}
>>> del a
>>> print(a)
Traceback (most recent call last):
File "C:\Users\mozhiyan\Desktop\demo.py", line 4, in <module>
print(a)
NameError: name 'a' is not defined

Python 自带垃圾回收功能,会自动销毁不用的字典,所以一般不需要通过 del 来手动删除。


深入解读 Key-Value 键值对

我们知道,字典中存放的就是具有映射关系的 key:value 无序数据,键值对可以很好的表征字典(Dict)的映射特性。

并且,键值对中的键(Key)必须是不可变类型(Number,Str 或者 Tuple,常用的是数值或字符串),且必须唯一;而值则可以是 Python 支持的任意类型对象。

映射数据场景下,通过 List 来比较一下 Dict 的优势:

[1] >>>> list && dict 场景模拟

举个例子,假设要根据公司员工的名字查找对应的薪资,如果用 list 实现,需要两个 list:

1
2
>>> names = ['Google', 'Baidu', '360', 'Opera']
>>> salary = [5888, 3888, 2888, 4300]

给定一个名字,要查找对应的薪资,就先要在 names 中找到对应的位置(索引),再利用索引从 salary 取出对应的成绩。且 list 越长,耗时越长。

1
2
3
>>> indx = names.index("Opera")
>>> salary[indx]
4300

但如果使用 dict 实现,只需要一个 名字:薪资 的对照表,直接根据名字查找薪资。

1
2
3
>>> staff = {'Google':5888, 'Baidu':3888, '360':2888, 'Opera':4300}
>>> staff["Opera"]
4300

相较于 List 的使用,键值对(key-value)的引入带来了访问性能上的提升,使得字典具有极快的查找速度。而且无论这个表有多大,查找速度都不会变慢。


[2] >>>> 键值对(Key-Value)检索模式

很迷惑啊!键值对到底干了啥?如何理解??? >>>> dict 之所以查找速度这么快,是因为其实现原理和查字典是一样的。

假设字典包含了 1 万个汉字,我们要查某一个字:

–> 第一种办法(list 方法):

把字典从第一页往后翻,直到找到我们想要的字为止,这种方法就是在 list 中查找元素的方法,list 越大,查找越慢。

–> 第二种方法(dict 方法):

先在字典的索引表里(比如部首表)查这个字对应的页码,然后直接翻到该页,找到这个字。无论找哪个字,这种查找速度都非常快,不会随着字典大小的增加而变慢。

字典(dict)就是第二种实现方式,给定一个名字,比如 Opera,dict 在内部就可以直接计算出 Opera 对应的存放薪资的“页码”,也就是 4300 这个数字存放的内存地址,直接取出来,所以速度非常快。

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


[3] >>>> Dict VS List

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

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

而 list 刚好相反:

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

所以,dict 是用空间来换取时间的一种方法,用在需要高速查找的很多地方。

[4] >>>> 键(Key)唯一不可变解释

正确使用 dict 非常重要,需要牢记的第一条就是 dict 的 key 必须是唯一、不可变对象

这是因为 dict 需要根据 key 来计算 value 的存储位置,如果每次计算相同的 key 得出的结果不同,那 dict 内部就完全混乱了。

这种通过 key 计算位置的算法称为哈希算法(Hash)。要保证 hash 的正确性,作为 key 的对象就不能变。


访问字典里的值

前面已经知道,字典(Dict)中必须根据 key 算出 value 的存放位置,即 key 对应了 value 元素对象的引用(索引),也就是说 dict 种需要根据 key 来获取 value 取值。

字典中的元素是无序的,每个元素的位置都不固定,所以字典不能像列表和元组那样,采用切片的方式一次性访问多个元素。

[1] >>>> dictname[key]

1
2
>>> print("dict["Google"]:",staff["Google"])
dict["Google"]: 5888

如果使用字典里没有的键访问数值时,会输出如下错误:

1
2
3
4
>>> staff["www"]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'www'

[2] >>>> dict 类型支持的 get() 方法

get() 方法的语法格式为:

1
dictname.get(key[,default])

其中,key 表示指定的键,default 用于指定要查询的键不存在时,此方法返回的默认值,如果不手动指定,会返回 None

示例:

1
2
3
4
5
6
7
>>> a = dict(two=0.65, one=88, three=100, four=-59)
>>> print( a.get('one') )
88

>>> a = dict(two=0.65, one=88, three=100, four=-59)
>>> print( a.get('five', None) )
None

| >>>> ===========================================================

推荐使用 get() 方法,可保证字典键值有效性 >>>>

1
2
3
4
5
6
7
8
staff = {'Google': 5888, 'Baidu': 3888, '360': 2888, 'Opera': 4300}
state = staff.get('4396'None
if not state:
....

# 等价于:
if '4396' not in staff or staff['4396'] is None or not staff['4396']:
...

成立有三种情况:

  • dict中不存在
  • dict中存在,但值是:None
  • dict中存在而且也不是 None,但是是一个等同于 False 的值,比如说空字符串或者空列表。

=========================================================== <<<< |


字典基本操作

字典(Dict)也是一种可变数据类型。所以我们可以单独对字典中的数据项(key:value 对)进行修改、增加、以及删除等操作。

常见的字典操作有以下几种:

  • 向现有字典中添加新的键值对;
  • 修改现有字典中的键值对;
  • 从现有字典中删除指定的键值对;
  • 判断现有字典中是否存在指定的键值对。

字典是由一个一个的 key-value 构成的,key 是找到数据的关键,Python 对字典的操作都是通过 key 来完成的。

[0] >>>> 字典容量查询

len(dict) 方法计算字典元素个数(获取字典容量),即键值对的总数:

1
2
3
>>> staff = {'Google': 5888, 'Baidu': 3888, '360': 2888, 'Opera': 4300, 'Sogo': 3200}
>>> len(staff)
5

[1] >>>> 字典添加键值对

把键值对放入 dict 的方法,除了初始化时指定外,为字典添加新的键值对很简单,直接给不存在的 key 赋值即可,具体语法格式如下:

1
dictname[key] = value

代码演示:

1
2
3
4
>>> staff = {'Google': 5888, 'Baidu': 3888, '360': 2888, 'Opera': 4300}
>>> staff["Sogo"] = 3000
>>> staff
{'Google': 5888, 'Baidu': 3888, '360': 2888, 'Opera': 4300, 'Sogo': 3000}

[2] >>>> 字典修改键值对

注意,由于字典种一个 key 只能对应一个 value,多次对一个 key 放入 value,后面的值会把前面的值冲掉,以此达到修改元素值的目的。请看下面的代码:

1
2
3
4
>>> staff = {'Google': 5888, 'Baidu': 3888, '360': 2888, 'Opera': 4300, 'Sogo': 3000}
>>> staff["Sogo"] = 3200 # 3000 --> 3200
>>> staff
{'Google': 5888, 'Baidu': 3888, '360': 2888, 'Opera': 4300, 'Sogo': 3200}

[3] >>>> 字典删除键值对

如果要删除字典中的键值对,还是可以使用 del 语句。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>> staff = {'Google': 5888, 'Baidu': 3888, '360': 2888, 'Opera': 4300, 'Sogo': 3200}

# 删除键值对:
>>> del staff["Sogo"]
>>> print(staff)
{'Google': 5888, 'Baidu': 4000, '360': 2888, 'Opera': 4300}

# 清空字典
>>> staff.clear()
>>> staff
{}


# 删除字典(实质上就是删除了用来存放字典的变量)
>>> del staff
>>> staff
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'staff' is not defined

[3] >>>> 判断字典中是否存在指定键值对

判断字典是否包含指定键值对的键,可以使用 innot in 运算符:

in 操作符用于判断键是否存在于字典中,如果键在字典 dict 里返回 true,否则返回 false。

not in 操作符刚好相反,如果键在字典 dict 里返回 false,否则返回 true。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> staff = {'Google': 5888, 'Baidu': 3888, '360': 2888, 'Opera': 4300}
>>> if 'Google' in staff:
... print("Key of Google exist")
... else:
... print("Key of Google not exist")
...
Key of Google exist

>>> if 'Sogo' in staff:
... print("Key of Sogo exist")
... else:
... print("Key of Sogo not exist")
...
Key of Sogo not exist

注意,key (not) in dict 常应用于判断某一 key 是否在字典中有定义。如果存在,由于通过键可以很轻易的获取对应的值,因此很容易就能判断出字典中是否有指定的键值对。


字典方法全攻略

Python 字典的数据类型为 dict,我们可使用 dir(dict) 来查看该类型包含哪些方法,例如:

1
2
>>> dir(dict)
['clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']

这些方法中,fromkeys()get() 以及 clear() 的用法上面有过说明,这里介绍剩下的方法:

[1] >>>> keys() && values() && items()

将这三个方法放在一起介绍,是因为它们都用来获取字典中的特定数据:

  • keys() 方法用于返回字典中的所有键(key);
  • values() 方法用于返回字典中所有键对应的值(value);
  • items() 用于返回字典中所有的键值对(key-value)。

代码演示:

1
2
3
4
5
6
7
>>> dict2 = {'one': 1, 'two': 2, 'three': 3}
>>> print(dict2.keys())
dict_keys(['one', 'two', 'three'])
>>> print(dict2.values())
dict_values([1, 2, 3])
>>> print(dict2.items())
dict_items([('one', 1), ('two', 2), ('three', 3)])

可以发现,keys()、values() 和 items() 返回值的类型分别为 dict_keysdict_valuesdict_items,它们都是可迭代对象(Iterable)。

在 Python 2.x 中,上面三个方法的返回值都是列表(list)类型。但在 Python 3.x 中,它们的返回值并不是我们常见的列表或者元组类型,因为 Python 3.x 不希望用户直接操作这几个方法的返回值。

故,你不能以索引的方式直接访问其中的数据:

1
2
3
4
>>> dict2.keys()[0]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'dict_keys' object is not subscriptable

如果想使用这三个方法返回的数据,一般有下面两种方案:

1)–> 使用 list() 函数,将它们返回的数据转换成列表,例如:

1
2
3
4
5
6
7
8
9
10
11
>>> keyslist = list(dict2.keys())
>>> keyslist
['one', 'two', 'three']
>>> valueslist = list(dict2.values())
>>> valueslist
[1, 2, 3]

# 返回的是一个由 key-value 元组组成的列表
>>> itemslist = list(dict2.items())
>>> itemslist
[('one', 1), ('two', 2), ('three', 3)]

2)–> for … in … 循环遍历

既然是可迭代对象(Iterable),故可以通过 for in 进行循环遍历:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> for key in dict2.keys():
... print(key, end=';')
...
one;two;three;

>>> for value in dict2.values():
... print(value, end=';')
...
1;2;3;

>>> for item in dict2.items():
... print(item, end=';')
...
('one', 1);('two', 2);('three', 3);

[2] >>>> copy()

copy() 方法返回一个字典的拷贝,也即返回一个具有相同键值对的新字典,例如:

1
2
3
4
>>> a = {'one': 1, 'two': 2, 'three': [1,2,3]}
>>> b = a.copy()
>>> print(b)
{'one': 1, 'two': 2, 'three': [1, 2, 3]}

可以看到,copy() 方法将字典 a 的数据全部拷贝给了字典 b

注意,copy() 方法所遵循的拷贝原理,既有深拷贝,也有浅拷贝(这和前面 list.copy() 方法是一样的)。事实上,这还有由于 Python 中元素对象属于不可变还是可变决定的,内容解读参见:Python 中的可变对象和不可变对象


[3] >>>> update()

update() 方法可以使用一个新的字典所包含的键值对来更新己有的字典。

更新原则:如果被更新的字典中己包含对应的键值对,那么原 value 会被覆盖;如果被更新的字典中不包含对应的键值对,则该键值对被添加进去。

代码演示:

1
2
3
4
>>> a = {'one': 1, 'two': 2, 'three': 3}
>>> a.update({'one':4.5, 'four': 9.3})
>>> print(a)
{'one': 4.5, 'two': 2, 'three': 3, 'four': 9.3}

[4] >>>> pop() && popitem()

pop()popitem() 函数都用来删除字典中的键值对,不同的是,pop() 用来删除指定的键值对,而 popitem() 用来随机删除一个键值对,它们的语法格式如下:

1
2
dictname.pop(key)
dictname.popitem()

代码演示:

1
2
3
4
5
6
7
8
9
>>> print(a) = {'one': 4.5, 'two': 2, 'three': 3, 'four': 9.3}
>>> a.pop("one")
4.5
>>> print(a)
{'two': 2, 'three': 3, 'four': 9.3}
>>> a.popitem()
('four', 9.3)
>>> print(a)
{'two': 2, 'three': 3}

注意,pop()popitem() 函数返回值为被删除的值。


[5] >>>> setdefault()

setdefault() 方法用来返回某个 key 对应的 value,其语法格式如下:

1
dictname.setdefault(key, defaultvalue)

setdefault() 方法总能返回指定 key 对应的 value:

  • 如果该 key 存在,那么直接返回该 key 对应的 value;
  • 当指定的 key 不存在时,setdefault() 会先为这个不存在的 key 设置一个默认的 defaultvalue,然后再返回 defaultvalue。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
a = {'数学': 95, '语文': 89, '英语': 90}
print(a)

# key不存在,指定默认值
a.setdefault('物理', 94)
print(a)

# key不存在,不指定默认值
a.setdefault('化学')
print(a)

# key存在,指定默认值
a.setdefault('数学', 100)
print(a)

运行结果如下:

1
2
3
4
{'数学': 95, '语文': 89, '英语': 90}
{'数学': 95, '语文': 89, '英语': 90, '物理': 94}
{'数学': 95, '语文': 89, '英语': 90, '物理': 94, '化学': None}
{'数学': 95, '语文': 89, '英语': 90, '物理': 94, '化学': None}

字典遍历 && 合并

基于上述我们了解的内容,实现常用的字典遍历 && 字典合并功能:

[1] >>>> 字典遍历

1)–> 成员检测方法:

1
2
3
4
5
>>> staff = {'Google': 5888, 'Baidu': 3888, '360': 2888, 'Opera': 4300}
>>> for key in staff:
... print(key, end=';')
...
Google;Baidu;360;Opera;>>>

2)–> key:value 键值对方法:

1
2
3
4
5
>>> staff = {'Google': 5888, 'Baidu': 3888, '360': 2888, 'Opera': 4300}
>>> for key,value in staff.items():
... print(key,":",value,end=';')
...
Google : 5888;Baidu : 3888;360 : 2888;Opera : 4300;

3)–> 键(Key)遍历方法:

1
2
3
4
5
6
>>> staff = {'Google': 5888, 'Baidu': 3888, '360': 2888, 'Opera': 4300}

>>> for key in staff.keys():
... print(key,end=';')
...
Google;Baidu;360;Opera;

[2] >>>> 字典合并

1)–> 常规方法:

1
2
3
4
5
6
7
8
>>> dict1 = {'a':1, 'b':2}
>>> dict2 = {'c':1, 'd':2}

>>> for key,value in dict1.items():
... dict2[key] = value
...
>>> dict2
{'c': 1, 'd': 2, 'a': 1, 'b': 2}

2)–> dict( list(dict1.items()) + list(dict2.items()) ) 方法:

1
2
3
4
5
6
>>> dict1 = {'a':1, 'b':2}
>>> dict2 = {'a':3, 'd':2}

>>> dict3 = dict(list(dict1.items()) + list(dict2.items()))
>>> dict3
{'a': 3, 'b': 2, 'd': 2}

3)–> 字典的 update 方法:

1
2
3
4
5
>>> dict5 = {}
>>> dict5.update(dict1)
>>> dict5.update(dict2)
>>> dict5
{'a': 3, 'b': 2, 'd': 2}

4)–> dict(dict1, **dict2) 方法:

1
2
3
4
5
6
>>> dict1 = {'a':1, 'b':2}
>>> dict2 = {'a':3, 'd':2}

>>> dict4 = dict(dict1, **dict2)
>>> dict4
{'a': 3, 'b': 2, 'd': 2}

集合(Set)

Python 中的集合,和数学中的集合概念一样(有限、互异、无序),用来保存不重复的元素。

你可以将其和 dict 进行比较,是一组 key 的集合,但不存储value。所以在 set 中,也没有重复的 key。和字典类似,Python 集合会将所有元素放在一对大括号 {} 中,相邻元素之间用 “,” 分隔,如下:

1
{element1,element2,...,elementn}

类似于 dict 中的 key 唯一性,所以同一集合中,只能存储不可变的数据类型,包括整形、浮点型、字符串、元组,无法存储列表、字典、集合这些可变的数据类型,否则 Python 解释器会抛出 TypeError 错误。比如说:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> {{'a':1}}
Traceback (most recent call last):
File "<pyshell#8>", line 1, in <module>
{{'a':1}}
TypeError: unhashable type: 'dict'
>>> {[1,2,3]}
Traceback (most recent call last):
File "<pyshell#9>", line 1, in <module>
{[1,2,3]}
TypeError: unhashable type: 'list'
>>> {{1,2,3}}
Traceback (most recent call last):
File "<pyshell#10>", line 1, in <module>
{{1,2,3}}
TypeError: unhashable type: 'set'

提示你数据类型错误,非哈希类型~~~


集合构建

Python 中提供了两种集合的创建方法:

[1] >>>> 集合的标准创建格式:

类似与 Dict,Python 提供了使用大括号 {} 创建集合,并用 , 分隔元素对象,创建格式如下:

1
2
3
parame = {value01,value02,...}
或者
set(value)

创建实例如下:

1
2
3
4
5
6
>>> set1 = {"Google", "Baidu", "Opera", "Baidu"}
>>> print(set1)
{'Opera', 'Google', 'Baidu'}
>>> set2 = {"Welcom", 32, 123.321}
>>> print(set2)
{32, 123.321, 'Welcom'}

[2] >>>> set() 内建函数创建集合:

除了使用 {} 创建列表外,Python 还提供了一个内置的函数 set(Iterable),使用它可以将其它可迭代对象转换为集合类型(自动去重)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> set3 = set("abcdefg")
>>> set3
{'d', 'c', 'g', 'f', 'e', 'b', 'a'}

>>> set4 = set(["Google", "Baidu", "Opera"])
>>> print(set4)
{'Opera', 'Google', 'Baidu'}

>>> set5 = set(("Google", "Baidu", "Opera", "Google"))
>>> print(set5)
{'Opera', 'Google', 'Baidu'}

>>> set6 = set({"one":1, "two":2})
>>> set6
{'one', 'two'}

空集合 >>>>

创建一个空集合必须用 set() 而不是 {...,... },因为 { } 是用来创建一个空字典的(已被占用)。

1
2
3
4
>>> dict1 = {}
>>> set1 = set()
>>> print(type(dict1),type(set1))
<class 'dict'> <class 'set'>

[3] >>>> 推导式方法创建列表:

类似列表、字典的推导式,同样,集合也支持集合推导式:

1
2
3
>>> setA = {x for x in 'abcdr' if x not in 'abc'}
>>> setA
{'d', 'r'}

集合的删除

和删除列表、元组、字典一样,手动删除字典也可以使用 del 关键字,例如:

1
2
3
4
5
6
7
8
>>> a = {1,'c',1,(1,2,3),'c'}
>>> a
{1, 'c', (1, 2, 3)}
>>> del a
>>> a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined

访问集合里的值

由于集合中的元素是无序的,因此无法向列表那样使用下标访问元素。

Python 中,访问集合元素最常用的方法是使用循环结构,将集合中的数据逐一读取出来:

1
2
3
4
5
>>> a = {1,'c',1,(1,2,3)}
>>> for ele in a:
... print(ele,end=' ')
...
1 c (1, 2, 3)

集合基本操作

集合(Set)也是一种可变数据类型。所以我们可以单独对集合中的数据项进行增加、删除以及集合之间做交集、并集、差集等运算。

[0] >>>> 集合容量查询

len(set) 方法可以来计算集合 set 元素个数。

1
2
3
>>> setA = {"Google", "Baidu", "Opera"}
>>> len(setA)
3

[1] >>>> 成员检查(in or not in)

x in set 方法可用于判断元素 x 是否在集合 set 中,存在返回 True,不存在返回 False。

1
2
3
>>> setA = {"Google", "Baidu", "Opera"}
>>> "Google" in setA
True

[2] >>>> 向 set 集合中添加元素

1)–> add()

集合方法 set.add( x ) 可以将元素 x 添加到集合 set 中,如果元素已存在,则不进行任何操作。

1
2
3
4
>>> setA = {"Google", "Baidu", "Opera"}
>>> setA.add("Sogo")
>>> print(setA)
{'Opera', 'Google', 'Baidu', 'Sogo'})

使用 add() 方法添加的元素,只能是数字、字符串、元组或者布尔类型(True 和 False)值,不能添加列表、字典、集合这类可变的数据,否则 Python 解释器会报 TypeError 错误:

1
2
3
4
Traceback (most recent call last):
File "C:\Users\mengma\Desktop\1.py", line 4, in <module>
a.add([1,2])
TypeError: unhashable type: 'list'

2)–> update()

set.update( x )方法也可以添加元素,且参数可以是字符串,列表,元组,字典,集合等:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
>>> setA = {"Google", "Baidu"}

>>> setA.update("Opera")
>>> setA
{'Baidu', 'r', 'p', 'Google', 'e', 'O', 'a'}

>>> setA.update([1,2],[3,4])
>>> setA
{1, 2, 3, 4, 'Baidu', 'r', 'p', 'Google', 'e', 'O', 'a'}

>>> setA.update(("Opera","360"))
>>> setA
{1, 2, 3, 4, '360', 'Baidu', 'r', 'Opera', 'p', 'Google', 'e', 'O', 'a'}

# 并集:合并集合
>>> setA.update({"Google", "Sogo"})
>>> setA
{1, 2, 3, 4, '360', 'Baidu', 'r', 'Opera', 'p', 'Google', 'Sogo', 'e', 'O', 'a'}

>>> setA.update({"jinshan":36})
>>> setA
{1, 2, 3, 4, '360', 'Baidu', 'r', 'Opera', 'p', 'Google', 'Sogo', 'e', 'O', 'a', 'jinshan'}

[3] >>>> 从 set 集合中删除元素

1)–> remove()

set.remove( x )方法将元素 x 从集合 set 中移除,如果元素不存在,则会发生错误。

1
2
3
4
5
6
7
8
9
>>> setA = {1, 2, 3, 4, '360', 'Baidu', 'r', 'Opera', 'p', 'Google', 'Sogo', 'e', 'O', 'a', 'jinshan'}
>>> setA.remove('jinshan')
>>> setA
{1, 2, 3, 4, '360', 'Baidu', 'r', 'Opera', 'p', 'Google', 'Sogo', 'e', 'O', 'a'}

>>> setA.remove(5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 5

2)–> discard()

set.discard( x ) 也是移除集合中的元素,且如果元素不存在,不会发生错误。

1
2
3
4
5
6
7
>>> setA = {"Google", "Baidu", "Opera"}
>>> setA.discard("Baidu")
>>> setA
{'Google', 'Opera'}
>>> setA.discard("360")
>>> setA
{'Google', 'Opera'}

3)–> clear()

clear() 方法用于移除集合中的所有元素。

1
2
3
4
>>> setA = {"Google", "Baidu", "Opera"}
>>> setA.clear()
>>> setA
set()

[4] >>>> 集合间运算

结合数学集合概念,我们来看两个集合间的运算:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 集合中元素自动去重
>>> setA = set("abcdefghabc")
>>> print(setA)
{'d', 'c', 'h', 'g', 'f', 'e', 'b', 'a'}
>>> setB = set("fghijklmjklm")
>>> print(setB)
{'h', 'm', 'g', 'i', 'j', 'f', 'k', 'l'}

# 差集:集合 setA 中包含而集合 setB 中不包含的元素
>>> setA - setB
{'d', 'c', 'e', 'b', 'a'}

# 并集:集合 setA 或 setB 中包含的所有元素
>>> setA | setB
{'d', 'c', 'h', 'g', 'm', 'i', 'j', 'f', 'e', 'k', 'b', 'l', 'a'}

# 交集:集合 setA 和 setB 中都包含了的元素
>>> setA & setB
{'h', 'f', 'g'}

# 互异:不同时包含于 setA 和 setB 的元素
>>> setA ^ setB
{'m', 'd', 'i', 'c', 'a', 'j', 'e', 'k', 'l', 'b'}

集合常用方法整理

[1] >>>> set.difference(set)

difference() 方法用于返回集合的差集,即返回的集合元素包含在第一个集合中,但不包含在第二个集合(方法的参数)中。

1
2
3
4
5
6
7
8
>>> setA = set("abcdefghabc")
>>> setB = set("fghijklmjklm")
>>> setA.difference(setB)
{'b', 'd', 'c', 'a', 'e'}

# 等价于 setA - setB
>>> setA - setB
{'b', 'd', 'c', 'a', 'e'}

[2] >>>> set.intersection(set1, set2 … etc)

intersection() 方法用于返回两个或更多集合中都包含的元素,即交集。

1
2
3
4
5
>>> x = {"a", "b", "c"}
>>> y = {"c", "d", "e"}
>>> z = {"f", "g", "c"}
>>> x.intersection(y,z)
{'c'}

[3] >>>> set.issubset(set) && set.issuperset(set)

issubset() 方法用于判断集合的所有元素是否都包含在指定集合中,如果是则返回 True,否则返回 False。即判断是否为子集关系。

1
2
3
4
5
>>> x = {"a", "b", "c"}
>>> y = {"f", "e", "d", "c", "b", "a"}
>>> z = x.issubset(y)
>>> print(z)
True

issuperset() 方法用于判断指定集合的所有元素是否都包含在原始的集合中,如果是则返回 True,否则返回 False。

1
2
3
4
5
>>> x = {"f", "e", "d", "c", "b", "a"}
>>> y = {"a", "b", "c"}
>>> z = x.issuperset(y)
>>> print(z)
True

5)set.union(set1, set2…)

union() 方法返回两个集合的并集,即包含了所有集合的元素,重复的元素只会出现一次。

1
2
3
4
5
6
>>> x = {"a", "b", "c"}
>>> y = {"f", "d", "a"}
>>> z = {"c", "d", "e"}
>>> result = x.union(y, z)
>>> result
{'b', 'c', 'f', 'a', 'd', 'e'}

Frozenset 集合

set 集合的不可变版本 >>>> frozenset 集合

set 集合是可变的,程序可以改变序列中的元素;frozenset 集合是不可变序列,程序不能改变序列中的元素。set 集合中所有能改变集合本身的方法,比如 remove()、discard()、add() 等,frozenset 都不支持;set 集合中不改变集合本身的方法,fronzenset 都支持。

我们可以在交互式编程环境中输入 dir(frozenset) 来查看 frozenset 集合支持的方法:

1
2
>>> dir(frozenset)
['copy', 'difference', 'intersection', 'isdisjoint', 'issubset', 'issuperset', 'symmetric_difference', 'union']

两种情况下可以使用 fronzenset:

  • 当集合的元素不需要改变时,我们可以使用 fronzenset 替代 set,这样更加安全。
  • 有时候程序要求必须是不可变对象,这个时候也要使用 fronzenset 替代 set。比如,字典(dict)的键(key)就要求是不可变对象。

程序演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> s = {'Python', 'C', 'C++'}
>>> fs = frozenset(['Java', 'Shell'])
>>> s_sub = {'PHP', 'C#'}

#set 集合中添加 frozenset
>>> s.add(fs)
>>> print('s =', s)
s = {'C', frozenset({'Java', 'Shell'}), 'C++', 'Python'}

# 向为 set 集合添加子 set 集合
>>> s.add(s_sub)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'set'

至此,关于 Python 基本数据类型的介绍已经完成,下面我们简单补充说明一下 Python 中的空值和不可变对象:

空值(None)

空值是 Python 里一个特殊的值,用None表示。None 不能理解为 0"" 等,因为 0 && ‘“”‘ 是有意义的,而 None是一个特殊的空值。

1
2
3
4
>>> None is []
False
>>> None is ""
False

None 是一个对象,其数据类型为 NoneType,其对应的 bool 值为 false。好比 0 是一个对象,其类型为 int,其 bool 值为 false。

1
2
>>> type(None)
<class 'NoneType'>

None 是 NoneType 数据类型的唯一值(其他编程语言可能称这个值为 null、nil 或 undefined),也就是说,我们不能再创建其它 NoneType 类型的变量,但是可以将 None 赋值给任何变量。

None 常用于 assert、判断以及函数无返回值的情况。对于所有没有 return 语句的函数定义,Python 都会在末尾加上 return None,使用不带值的 return 语句(也就是只有 return 关键字本身),那么就返回 None。


可变、不可变对象

前面我们说过,str 是不变对象,而 list 是可变对象。

对于可变对象,比如 list,对 list 进行操作,list 内部的内容是会变化的,比如:

1
2
3
4
>>> list1 = ['c', 'b', 'a']
>>> list1.sort()
>>> list1
['a', 'b', 'c']

而对于不可变对象,比如 str,对 str 进行操作呢:

1
2
3
4
5
>>> str1 = 'abc'
>>> str1.replace('a', 'A')
'Abc'
>>> str1
'abc'

虽然字符串有个replace()方法,也确实变出了'Abc',但变量str1最后仍是'abc',应该怎么理解呢?

再来看:

1
2
3
4
5
6
>>> str1 = 'abc'
>>> str2 = str1.replace('a', 'A')
>>> str1
'abc'
>>> str2
'Abc'

事实上,str1 是变量,而 'abc' 才是字符串对象!有些时候,我们经常说,对象 str1 的内容是 'abc',但其实是指,str1 本身是一个变量,它指向的对象的内容才是 'abc'

当我们调用 str1.replace('a', 'A') 时,实际上调用方法 replace 是作用在字符串对象 'abc' 上的,而这个方法虽然名字叫 replace,但却没有改变字符串 'abc' 的内容。相反,replace 方法创建了一个新字符串 'Abc' 并返回,如果我们用变量 str2 指向该新字符串,就容易理解了,变量 str1 仍指向原有的字符串 'abc',但变量 str2 却指向新字符串 'Abc' 了。

所以,对于不变对象来说,调用对象自身的任意方法,也不会改变该对象自身的内容。相反,这些方法会创建新的对象并返回,这样,就保证了不可变对象本身永远是不可变的。而可变对象就不一定了。

关于 Python 中的可变和不可不对象内容的详细解读请参见:Python 中的可变对象和不可变对象


Author

Waldeinsamkeit

Posted on

2018-01-07

Updated on

2022-03-17

Licensed under

You need to set install_url to use ShareThis. Please set it in _config.yml.

Comments

You forgot to set the shortname for Disqus. Please set it in _config.yml.