前言:
我一直觉得对我来说学习知识很忌讳不系统. 本篇内容与上一篇 自定义序列类是有联系的.
上一篇比较通范的了解了序列类的一些协议和特性, 并且有些 list 的内容. 这篇更加具体到 set 和 dict 这两个序列类.
以此来了解 python 序列类的具体应用.(这篇比较简单)(感觉具体比抽象都更容易理解, 但是也要学会思考把具体对象抽象化来表达, 即提取共性)
content:
1.dict 在 abc 中的序列类型和继承关系
2.dict 实现了的常用方法
3. 我可不可以继承 dict 这种序列类?
4.set 和 frozenset
5.set 和 dict 的原理
==============
1.dict 在 abc 中的序列类型和继承关系
dict 在 collection.abc 中, 实际上是属于 MutableMapping(可变 mapping) 类型.
跟上篇对可变序列类继承的分析一样, MutableMapping 继承了 Mapping 的一些功能并且加了一些可变的特性,
Mapping 继承了 Collection. 接下来的继承和上篇的一样.
2.dict 实现了的常用方法
如果用的是 pycharm, 还是用 ctrl+b 就能跳到 python 对 dict 的定义.
常用:
- a = {"1":{"a":"aa"},
- "2":{"b":"bb"}}
- # 清空字典
- a.clear()
- # 浅拷贝字典 浅拷贝虽然可以正常赋值, 但是如果 my_dopy_dict 中的值进行了改变, 则 a 中的值也会进行对应的改变
- my_dopy_dict = a.copy()
- # 深拷贝 深拷贝则是实实在在的在内存当中声明了一个新的变量
- import copy
- new_dict = copy.deepcopy(a)
- # get 函数 dict.get(要查找的 key, 如果没找到对应 key 的内容返回的数据)
- print(a.get("3",{1:"3"})) # {1: '3'}
- # dict.fromkeys() 函数用于创建一个新字典, 以序列 seq 中元素做字典的键 seq 可以是可迭代的, value 为字典所有键对应的初始值.
- my_list = [1, 2, 3]
- my_new_dict = dict.fromkeys(my_list, {"222":"3434"}) #{1: {'222': '3434'}, 2: {'222': '3434'}, 3: {'222': '3434'}}
- # setdefault() 函数和 get() 方法 类似,
- # 如果键不存在于字典中, 将会添加键并将值设为默认值.
- # 如果存在, 则将会返回该 key 对应的 value
- a.setdefault("3", "cc") # a= {'1': {'a': 'aa'}, '2': {'b': 'bb'}, '3': 'cc'}
- print(a.setdefault("2", "cc")) # 返回 {'b': 'bb'}
- # update() 函数把字典 dict2 的键 / 值对更新到 dict 里.
- # 如果字典 b 中有与 a 相同的 key, 则会把 a 中的 key 对应的 value 进行更新
- # 如果字典 b 中有 a 中没有的 key, 则 a 会将未有的 key 与 value 添加进去
- b = {"3": "cc", "2": "dd"}
- a.update(b)
- print(a) # {'1': {'a': 'aa'}, '2': 'dd', '3': 'cc'}
3. 我可不可以继承 dict 这种序列类?(dict 的子类)
a. 如果我偷懒想实现 dict 这种类型, 能不能直接继承这种序列类呢? 同理 list 是否可以?
例: 继承 dict, 并且重写设置 dict key 的 value 时调用的魔法函数, 使其值变为 2 倍
- class Mydict(dict):
- def __setitem__(self, key, value):
- super().__setitem__(key, value*2)
- a=Mydict(b=1)
- print(a)
- a['b']=1
- print(a)
输出:
可以发现, 原来同样功能和效果的, 我们重写方法后, 第一种方法去设置 key 的 value 值这一操作并没有调用我们重写的方法.
所以并不建议去继承 python 的这种序列类.
b. 有没有什么办法我实在想继承?
python 里专门给了个 UserDict 类, 可以实现想要的继承 Dict 类的效果
from collections import UserDict class Mydict(UserDict): def __setitem__(self, key, value): super().__setitem__(key, value*2) mydict = Mydict(one = 1) # {'one': 2} 调用__setitem__这个魔法函数 mydict["one"] = 2 # {'one': 4} 这种方式也可以调用__setitem__
输出:
c.python 中 Dcit 实际也有子类实现: defaultdict
使用:
from collections import defaultdict # 这个是 dict 的子类 mydict = defaultdict(dict) myvalue = mydict["bai"] # 如果不存在的话, 返回 { }
输出:
4.set 和 frozenset
a. 两者是啥有啥特点?
set: 集合 (无序, 不重复, 可变)
frozenset: 不可变集合 (无序, 不重复, 不可变)
frozenset 没有改变的方法 一旦初始化了 就不可变了. 其中, 不可变类型对可变类型的一个特点, 不可变类型是可以作为 dict 的 key 的.
b.set 常用操作
a=set('abcdee') a.add('f') print(a) another_set=set('defgh') # 添加数据 #a.update(another_set) #print(a) # 集合的差集 re_set=a.difference(another_set) # 减法实现于__ior__魔法函数 re_set2=a-another_set # 集合的交集 & re_set3=a&another_set # 集合的并集 | re_set4=a|another_set print(re_set) print(re_set2) print(re_set3) print(re_set4) # 也可以用 if in 判断 (实现于__contains__魔法函数) if 'a' in re_set: print('I am a set')
5.set 和 dict 的原理
之前就提过, set 的性能棒的. dict 的查询性能远比起 list 要好.
并且 list 中随着 list 数据的增大, 查找时间会增大, 而 dict 不会.
这是为什么呢?
因为 dict 使用 hash 这种数据结构存储. set 也是.
a.dict 的散列表
特点:
- dict 的 key 必须是可 hash 的, 不可 hash 的是不能当成 dict 的 key 的, 比如 list
- python 申请内存的时候, 会初始化一个小的连续的空空间
- 由上一条可得这样 一定会存在浪费, 这时候每次变动操作 会计算剩余空间, 剩余空间小于 1/3 的时候, 会重新生成空间并且将现在数据都拷贝过去 (rehash)
- 数组比这种链表结构好的地方, 就是可以直接根据偏移量来存取, 而不用全部开始从头遍历.
(这种结构 hash 和重 hash 的策略用在很多地方, 包括 Redis 等)
b. 存储结构了解了, 那么数据的查找过程呢?
先计算 a 的散列值, 查找表源是否为空,
因为 a 是不变的, 所以如果表源为空, 那么就会抛出 key error.
如果表源不为空, 也有可能是其他 key, 查看 key 是否是要查找的 key.
如果是其他 key, 重新散列循环查找.
c. 这种 hash 结构在 ptthon 中的特点
- 我们可以用__hash__这个魔法函数实现可 hash 对象
- dict 内存开销比较大, 这是 hash 表的特点.
- 实际上 python 内部类和对象, 都是 dict.
- dict 存储顺序和元素添加顺序有关.
- 插入数据后检查剩余空间引发的重 hash, 会影响原来的数据 (比如地址).
来源: https://www.cnblogs.com/besttr/p/11531222.html