[Python] 关于字典方法 fromkeys() 的踩坑和理解

fromkeys()方法以序列 seq 中元素做字典的键,value 为字典所有键对应的初始值。
语法:
       dict.fromkeys(seq,value=None)
参数:
       seq -- 字典键值列表。
value -- 可选参数, 设置键序列(seq)对应的值,默认为 None。
返回值:
       返回一个新字典。

[例]
想要创建一个这样的字典:

>>> dict_28star={'青龙': ['角', '亢', '氐', '房', '心', '尾', '箕'], \
'玄武': ['斗', '牛', '女', '虚', '危', '室', '壁'],  \
'白虎': ['奎', '娄', '胃', '昴', '毕', '参', '觜'],  \
'朱雀': ['井', '鬼', '柳', '星', '张', '翼', '轸']}
>>> 
>>> dict_28star
{'青龙': ['角', '亢', '氐', '房', '心', '尾', '箕'], '玄武': ['斗', '牛', '女', '虚', '危', '室', '壁'], '白虎': ['奎', '娄', '胃', '昴', '毕', '参', '觜'], '朱雀': ['井', '鬼', '柳', '星', '张', '翼', '轸']}

使用方法fromkeys()如下:

1.创建时不赋值,值为None

>>> dict1={}
>>> seq = ('青龙', '玄武', '白虎','朱雀')
>>> dict1=dict1.fromkeys(seq)
>>> dict1
{'青龙': None, '玄武': None, '白虎': None, '朱雀': None}
>>>

然后依次赋值,

>>> dict1['青龙']=['角', '亢', '氐', '房', '心', '尾', '箕']
>>> dict1['玄武']=['斗', '牛', '女', '虚', '危', '室', '壁']
>>> dict1['白虎']=['奎', '娄', '胃', '昴', '毕', '参', '觜']
>>> dict1['朱雀']=['井', '鬼', '柳', '星', '张', '翼', '轸']
>>> dict1
{'青龙': ['角', '亢', '氐', '房', '心', '尾', '箕'], '玄武': ['斗', '牛', '女', '虚', '危', '室', '壁'], '白虎': ['奎', '娄', '胃', '昴', '毕', '参', '觜'], '朱雀': ['井', '鬼', '柳', '星', '张', '翼', '轸']}
>>> 

可以看到得到的是想要的效果,这里顺便查以下4个值对应的内存地址(会发现是不一样的地址),方便稍后比较

>>> for v in dict1.values(): print(id(v))

1533561286792
1533564252744
1533561281480
1533561281096
>>> 

2.创建时赋值,4个键会共用一个值,准确来说是指向同一个位置,可以用id(value)看4个值指向的内存位置,发现是一样的

>>> dict2={}
>>> dict2=dict2.fromkeys(seq,['角', '亢', '氐', '房', '心', '尾', '箕'])
>>> dict2
{'青龙': ['角', '亢', '氐', '房', '心', '尾', '箕'], '玄武': ['角', '亢', '氐', '房', '心', '尾', '箕'], '白虎': ['角', '亢', '氐', '房', '心', '尾', '箕'], '朱雀': ['角', '亢', '氐', '房', '心', '尾', '箕']}
>>> 
>>> for v in dict2.values(): print(id(v))

1533561281288
1533561281288
1533561281288
1533561281288
>>> 

尝试更新 '玄武' 键的值,更新成功,查看其内存地址,指向了别处。

>>> dict2['玄武']=['斗', '牛', '女', '虚', '危', '室', '壁']
>>> dict2
{'青龙': ['角', '亢', '氐', '房', '心', '尾', '箕'], '玄武': ['斗', '牛', '女', '虚', '危', '室', '壁'], '白虎': ['角', '亢', '氐', '房', '心', '尾', '箕'], '朱雀': ['角', '亢', '氐', '房', '心', '尾', '箕']}
>>> for v in dict2.values(): print(id(v))

1533561281288
1533564331336
1533561281288
1533561281288
>>> 

用同样的方法可以更新 '白虎' 键的值和 '朱雀' 键的值。

3.看着没什么问题,再玩一玩会发现有坑的,例如我们不想用这种直接赋值的方法改变键的值,就是想用用给append()或者给二级元素一个个赋值的方法呢?下面踩踩坑~

使用 fromkeys() 赋值的列表里只有一个值,创建好字典后再append。这里先考虑 '青龙',给它的值(一个列表)append第二个元素 '亢',然后会发现对4个值做了一样的操作。

>>> dict3={}
>>> dict3=dict3.fromkeys(seq,['角'])
>>> dict3
{'青龙': ['角'], '玄武': ['角'], '白虎': ['角'], '朱雀': ['角']}
>>> dict3['青龙'].append('亢')
>>> dict3
{'青龙': ['角', '亢'], '玄武': ['角', '亢'], '白虎': ['角', '亢'], '朱雀': ['角', '亢']}
>>> for v in dict3.values(): print(id(v))

1533564587528
1533564587528
1533564587528
1533564587528
>>> 

给值(列表)中的元素赋值,4个列表也一起变

>>> dict3['玄武'][0]='斗'
>>> dict3
{'青龙': ['斗', '亢'], '玄武': ['斗', '亢'], '白虎': ['斗', '亢'], '朱雀': ['斗', '亢']}
>>> for v in dict3.values(): print(id(v))

1533564587528
1533564587528
1533564587528
1533564587528
>>> 

对值(列表)使用 pop(),4个列表也一起变

>>> dict3['白虎'].pop()
'亢'
>>> dict3
{'青龙': ['斗'], '玄武': ['斗'], '白虎': ['斗'], '朱雀': ['斗']}
>>> for v in dict3.values(): print(id(v))

1533564587528
1533564587528
1533564587528
1533564587528
>>> 

将值(列表)清空,4个列表也一起清空

>>> dict3['朱雀'].clear()
>>> dict3
{'青龙': [], '玄武': [], '白虎': [], '朱雀': []}
>>> for v in dict3.values(): print(id(v))

1533564587528
1533564587528
1533564587528
1533564587528
>>> 

其实道理很简单,上述过程中,4个键的值(列表)所指向的一直是内存中的同一块区域,就像公用的房间,谁装修啊拆墙啊效果大家都看得见~

这个有点类似浅拷贝(传送门 -> Python 直接赋值、浅拷贝和深度拷贝解析
https://www.runoob.com/w3cnote/python-understanding-dict-copy-shallow-or-deep.html)

所以这时如果直接改变某个键的值,而非给值(列表)中的元素赋值,就不会有这种副作用。如下,改变 '玄武' 的值没有影响到别人,因为其值指向了别处,可以看到它的内存位置更新了,也就是它搬家啦,它在自己的新家里购置家具,曾经的室友当然看不见咯~

>>> dict3['玄武']=['斗', '牛']
>>> dict3
{'青龙': [], '玄武': ['斗', '牛'], '白虎': [], '朱雀': []}
>>> for v in dict3.values(): print(id(v))

1533564587528
1533564586888
1533564587528
1533564587528
>>> 

4.那有什么应对措施吗?

首先想到的就是深拷贝,但是这种方法行不通的,因为地址虽然是新的,但是还是‘玄武’一个房间,其它三人一个房间。上文提到的“类似浅拷贝”是指,这4个列表之间的关系类似浅拷贝,把整个字典深拷贝是不能解决这个问题的。

>>> import copy
>>> dict4=copy.deepcopy(dict3)
>>> for v in dict4.values(): print(id(v))

1533564585992
1533561287368
1533564585992
1533564585992
>>> 

害,其实这样创建字典不也挺麻烦吗?还不如像开头那样直接创建字典 dict_28star并同时赋值,或者像1或2那样给键的值直接赋值,省事、快捷、不留坑。

或者用个循环咯,四个人一人一屋,互不影响

>>> seq
('青龙', '玄武', '白虎', '朱雀')
>>> dict5={key: [] for key in seq}
>>> for v in dict5.values(): print(id(v))

1533564597192
1533564597000
1533564596744
1533564331720
>>> 

所以目前看来 dict.fromkeys() 应该是适合那些字典中的值要保持一致的情况吧。比如吃火锅,大家点的东西都会在锅里或者即将在锅里哈哈哈~

 

  • 17
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值