Python字典赋值还在踩坑?这个默认值技巧让你代码少写一半

📅 2026/7/13 22:00:02 👁️ 阅读次数 📝 编程学习
Python字典赋值还在踩坑?这个默认值技巧让你代码少写一半

数据结构里, 将字典dict予以运用时, 其出现频率是相当之高的, 针对它的运用方法, 有着诸多细微的诀窍。若能把这些诀窍掌握住, 那你运用dict的时候就会效率超高, 并且会让你的代码变得更简洁。

1.默认值

假设存放的是name和id的映射关系:

name_for_userid ={1:'张三',2:'李四',3:'王五', }

获取中的某一个id的name,最简单的方式:

name_for_userid[1]
'张三'

这么一种方式, 虽说简单, 然而存在着一个不太便利的地方, 那便是, 要是去获取一个并不存在于其中的值, 程序便会出现异常。

name_for_userid[4]---------------------------------------------------------------------------KeyError Traceback (most recent call last)in()----> 1 name_for_userid[4] KeyError:4

多数情形下我们切勿期望程序呈现这般异常为了防止此种状况能够于获取name前先行判定该id是否业已存在于其中。

if4inname_for_userid:print(name_for_userid[4])else:print("Not Found")
"Not Found"

这种写法虽说具备可行性, 然而其效率欠佳, 之所以如此, 是由于获取某一个id所对应的name最多情况下需要进行两次查询操作: 第一次是先对该id是否存在进行判断, 若存在的话, 那么第二次则从中将name值取出来。另外还存在一种写法:

try:print(name_for_userid[4])exceptKeyError:print("Not Found")
"Not Found"

在这种写法当中, 代码存在着一定程度的冗余情况, 未能展现出足够的简洁性, 所以呢, dict提供了一种名为get的方法, 该方法具备这样的好处, 那就是能够设置值, 针对那些在dict里并不存在的key, 它会返回某个值, 将此值作为这个key的value。

name_for_userid.get(4,"None")
"None"

2.排序

card = {'a': 4,'c': 2,'b': 3,'d': 1}

用对card排序,其实是用card的key进行排序,如下:

sorted(card.items())
[('a', 4), ('b', 3), ('c', 2), ('d', 1)]

有的时候, 我们得针对card的value予以排序, 在这种时候, 便能运用函数里的key这个参数, 我们能对这个函数的用法给予帮助:

help(sorted)
Help on built-infunction sortedinmodule builtins: sorted(iterable, key=None, reverse=False) Return a new list containing all itemsfromthe iterableinascending order.A custom key function can be supplied to customise the sort order,andthe reverse flag can be set to request the resultindescending order.

所以我们可以自定义一个key函数:

defsorted_by_value(item):returnitem[1] sorted(card.items(),key=sorted_by_value)
[('d', 1), ('c', 2), ('b', 3), ('a', 4)]

其实,如果使用匿名函数的话,代码会更简洁:

sorted(card.items(),key=lambdaitem:item[1])

如果让别人更轻松的理解你的意图,你可以尝试用下面的方法:

importoperator sorted(card.items(),key=operator.itemgetter(1))

要留意的是, .函数所获取的并非值, 反倒定义了一个函数, 借助该函数作用于对象之上以后, 才能够获取该对象上指定域的值。

实际上, 相较而言是更为倾向于表达式的, 缘由在于其具备灵活性, 且呈现出简洁的特性, 举例来说, 要是需要依据value的绝对值来对dict进行排序的话, 那么便能够像下面这样去书写:。

sorted(card.items(),key=lambdaitem:abs(item[1]))

3....case

据一句流传许久的话语来讲, 往昔、当下以及往后的任何时段之中, 定然不会存在……case语句。之所以会出现这样的状况, 那是由于if…elif…else以及dict均可达成……case语句所具备的功能, 因而……case便丧失了存在下去的必要之处了。

ifcond =='cond_a': handle_a()elifcond =='cond_b': handle_b()else: handle_default()

要是用if...elif...else去达成特定功能, 其优势在于具备较强的可读性, 然而倘若所处理的条件是比较多的那种情况, 如此这般的写法便会显得有一些啰里啰嗦且存在冗余现象。所以呢, 到了这个时候dict就又一次闪亮登场了。

func_dict ={'cond_a': handle_a,'cond_b': handle_b } cond = 'cond_a' func_dict[cond]()

和if...elif...else相比较而言, dict看起来要清爽不少, 除此之外, 要是我们想要达成, 也能够运用dict的get()方法:

>>>func_dict.get(cond,handle_default)()

这样即使cond不在中,程序也不会异常中止。

下面再举一个例子,如下

>>>def dispatch_if(operator, x, y): if operator == 'add': return x + y elif operator == 'sub': return x - y elif operator == 'mul': return x * y elif operator == 'div': return x / y
>>>defdispatch_dict(operator, x, y):return{'add':lambda: x +y,'sub':lambda: x -y,'mul':lambda: x *y,'div':lambda: x /y, }.get(operator,lambda: None)()

要是想得更深入些, 从性能方面来讲, 上面所举的例子并非是最优的, 毕竟, 每次在调用函数之际, 都会去生成一个临时的、涵盖各种操作码(加减乘除)的字典, 而最好的情形无疑是这个字典仅仅生成一回(常量), 当下次再度调用这个函数的时候, 直接运用这个字典便行了, 此外, 中的模块已然实现了加减乘除, 比如: .mul、.div , 完全能够替代表达式, 这里仅仅是举个例子, 旨在更妥善地明晰 if…elif…else 和 dict 的异同之处。

4.合并dict的几种方法

有些时候, 会存在借助一个dict去对另一个dict予以更新的情况, 像是, 利用用户自行定义的配置文件去将默认配置进行覆盖之类的。假定存在下面这样两个dict:

>>> xs = {'a': 1,'b': 2}>>> ys = {'b': 3,'c': 4}

最常用的就是用dict内置的()方法:

>>> zs ={}>>>zs.update(xs)>>> zs.update(ys) >>> zs {'a': 1, 'b': 3, 'c': 4}

还有一种方法就是使用内置的dict():

>>> zs = {**xs, **ys} {'a': 1,'b': 3,'c': 4}

5.美观打印

在进行打印一些调试信息的操作时, 美观的打印输出, 有时能够让人们很直观地看出关键信息, 进而提高调试的效率。

最朴素的打印:

>>> mapping = {'a': 23,'b': 42,'c': 0xc0ffee}>>>str(mapping)"{'c': 12648430, 'a': 23, 'b': 42}"

借助内置模块json可以实现更加直观的表现形式:

>>>importjson>>> json.dumps(mapping, indent=4, sort_keys=True) {"a": 23,"b": 42,"c": 12648430}

但是这种方法有一定的限制:

>>> mapping['d'] = {1, 2, 3}>>>json.dumps(mapping) TypeError:"set([1, 2, 3]) is not JSON serializable">>> json.dumps({all:'yup'}) TypeError:"keys must be a string"

所以,还可以使用下面的方式:

>>>importpprint>>>pprint.pprint(mapping) {'a': 23,'b': 42,'c': 12648430,'d': set([1, 2, 3])}

6.彩蛋

最后介绍几个关于dict比较有意思的东西 .

>>>print("keys:",list({True:'yes', 1:'no', 1.0:'maybe'}.keys())) >>>print("values:",list({True:'yes', 1:'no', 1.0:'maybe'}.values()))

上面的代码会打印出什么结果呢,一开始我认为是下面的输出:

keys: [True,1,1.0] values: ['yes','no','maybe']

真是too young too ,其实结果应是:

keys: [True] values: ['maybe']

缘由何在, 若再度审慎思索, 鉴于键为真, 那么值为何并非肯定而是或许, 进而, 鉴于值为或许, 那键值为何不是一点零却是真, 汝等之疑惑是否与吾相同。

其实, 在处理dict表达式之际, 它会先依据dict类去创建出dict的实例, 随后依照item(key:value)于dict表达式里出现的顺序逐个进行赋值, 如下:

>>>xs =dict() >>>xs[True]='yes'>>>xs[1] ='no'>>>xs[1.0] ='maybe'

更让人觉得奇怪的是, dict会凭借下面的表达式, 判定他的key值全部都是相等的, 是这样的情况, 没错的, 就是这样。

>>>True == 1 == 1.0
True

见到1等于1.0兴许你不会觉得诧异, 然而为何会出现True等于1的情况? 那时看到此处我也存有几分困惑之感。

翻阅了一下的官方文档,在这一章节中发现下面这段话:

这个类型是那个类型中的一种类型, 并且如同0和1那样, 在所有情况里, 这种情况是当涉及某一事物时, “假”或“真”是被涉及的。标点使用有些杂乱奇怪, 不太能完全理解这段话确切意思, 勉强按要求改写了。

意思是讲,它属于Int的子类, 而这个子类所包含的就是0与1;进而来讲, 我们能够对此进行一番验证。

>>>["No","Yes"][True]

结果果然是Yes,

>>>["No","Yes"][False]

结果也果然是No.但是为了清晰起见,不建议这样用.

看到这里你可能会问,这和dict有毛关系.接着往下看:

就特定情况而言, True、1以及1.0均指示相同的字典键值, 当解释器于执行字典表达式之际, 它会将后续同一个键(True)的取值覆盖掉此键(True)先前的取值, 所以便会出现如下的结果:

>>>{True:'yes', 1:'no', 1.0:'maybe'} {True:'maybe'}

但是, 为何key没被最后的那个1.0给覆盖掉? 实际上, 道理是蛮简单的, 既然是同样的key, 那为啥还要做额外多余这不必要之事, 耗费多余的时间去更新‘相同的’key? 这是基于解释器性能方面的考量。

>>>ys = {1.0:'no'} >>>ys[True]='yes'>>>ys输出:{1.0:'yes'}

依据我们当前所知晓的情况, 从表面状况而言, 是在key值相同之际, 才去覆盖已存在的value值, 然而, 实际情况表明, 这并非仅仅与判定相等有关联, 还有其他因素起作用了, 事实就是如此, 有相关后续影响的。

字典借助哈希表得以实现, 这意味着, 它借由将 key 映射至表内一处位置去访问记录, 从而加快查找之速度。此映射函数称作哈希函数, 存放记录的数组名为哈希表。然而并不存在十分完美的哈希函数, 经其计算出的存储位置皆不相同, 故而会出现哈希冲突之情形, 亦即不同的 key 计算出的存储位置为同一地址。接下来瞧瞧究竟是何种因素致使更新 value 值。

>>>classAlwaysEquals:def__eq__(self, other):returnTruedef__hash__(self):returnid(self)
AlwaysEquals有两个方法:__eq____hash__. 首先,由于__eq__返回True,所以下面的条件表达式都是True:
>>>AlwaysEquals() == AlwaysEquals() >>>AlwaysEquals() == 42 >>>AlwaysEquals() == 'waaat?'

其次,由于返回该对象的id值,也就是内存地址,所以有:

>>>objects =[AlwaysEquals(),AlwaysEquals(),AlwaysEquals()] >>>[hash(obj)forobjinobjects]
[140388524604608, 140388524604664, 140388524604720]

所以, 综上所述, 在值相同然而哈希值不一样的情况下, 会不会出现值被覆盖的情况呢:

>>>{AlwaysEquals():'yes', AlwaysEquals():'no'}

结果是:

{<__main__.AlwaysEquals at 0x7faec023bbe0>:'yes',<__main__.AlwaysEquals at 0x7faec023bac8>:'no'}
>>>classSameHash:def__hash__(self):return1>>>a=SameHash() >>>b= SameHash()

由于返回1,所以有:

>>> a ==b False>>>hash(a), hash(b) (1, 1)

当值不一样可是hash值相同之际, 是不是会有value值被覆盖的情况呢:

>>> {a:'a', b:'b'} {:'a',:'b'}

综上这两种情况,value值都不会被更新。

而特定集合{True: 'yes', 1: 'no', 1.0: 'maybe'}, 会出现更新键所对应的取值发生变化的情况, 具体原因在于:

>>> True == 1 == 1.0True>>> (hash(True), hash(1), hash(1.0)) (1, 1, 1)

他们同时满足条件表达式相等且hash值也相等。