揭秘 Python 字典的有趣特性

VOL.11283 views

1

Mar. 2024

Python中的字典是一种灵活的数据结构,提供了高效的键值映射。尽管它们通常用于简单的查找和存储数据,但还有一些有趣且不太常见的方面和用例值得探索:

1、将列表作为字典的键

>>>_dict = {}
>>>_list = [1, 2, 3]
>>>_dict[_list] = 'Added'

#输出:
# 'list'类型不可被hash
TypeError: unhashable type: 'list'

事实上,如果我们将列表作为字典的键,上述代码会引发错误。原因是当我们将一个对象作为字典的键时,Python会调用该对象类的 __hash__ 函数。

intstrtuple等不同,列表类缺少 __hash__方法的实现。

如果我们尝试扩展 list 类,在其中添加__hash__方法,那么它将允许我们用列表作为字典的键。

现在如果我们尝试扩展列表类并在其中添加这个方法,它将允许我们将列表作为字典的键。

class ClassList(list):
    def __hash__(self):
        return 0

_dict = {}
_list = ClassList([1, 2, 3])
_dict[_list] = 'Added'
print(_dict)

输出:
{[1, 2, 3]: 'Added'}

但并不推荐使用上述方法,因为这将使列表可哈希化,从而导致其他不可预知的程序行为。

2、使用字典作为IF条件的替代方案

我们都知道字典的用途是维护键值对。但是,字典还可以作为IF条件的特殊用例使用。例如,考虑以下代码。根据输入值,调用相应的函数。

def funca():
    print('a')

def funcb():
    print('b')

num = 1
if num==1:
    funca()
elif num==2:
    funcb()
else:
    print('error')

# 结果
a

使用字典:

def funca():
    print('a')

def funcb():
    print('b')

num = 1
funcMaping = {1: funca,2:funcb}
funcMaping.get(num,lambda:print('error'))() 
# lambda:print('error')为默认函数

# 结果
a

可以看到,使用字典可以很容易的找到并执行对应的函数,同时这也提高了代码的可维护性。冗长的if-else通常是难以维护的代名词。

3、字典作为 switch 语句

您可以使用字典模拟switch语句,以获得更清晰和更易读的代码。例如:

def switch_case(case):
    return {
        'case1': 'This is case 1',
        'case2': 'This is case 2',
        'default': 'This is the default case'
    }.get(case, 'Invalid case')

result = switch_case('case1')

以上代码将打印:“This is case 1”。

4、__missing__方法

从Python 2.5 开始,字典就加入了一个特殊的 __missing__方法,用于处理缺失的项:

class MyDict(dict):
    def __missing__(self, key):
        self[key] = rv = []
        return rv

m = MyDict()
m["foo"].append(1)
m["foo"].append(2)

print(dict(m))  # {'foo': [1, 2]}
print(m["x"])   # []

其实 python 还提供了字典的一个子类Defaultdict,功能与上述方法类似,只是对于不存在的项,它调用了一个无参数的函数:

from collections import defaultdict

m = defaultdict(list)
m["foo"].append(1)
m["foo"].append(2)

print(dict(m))  # {'foo': [1, 2]}

当您想提供默认值或在找不到键时执行特定操作而不是引发KeyError时,这非常有用。

5、哈希等价键

下面是一个有趣的字典示例:

my_dict = {'1': 'string', True: 'bool', 1: 'int', 1.0: float}
print(my_dict)

输出:
{'1': 'string', True: <class 'float'>}

尽管向 Python 字典添加了 4 个不同的键,但你能告诉为什么它只保留了其中的两个吗?

原因是在Python中,字典根据哈希等价性(使用hash()计算)而不是身份(使用id()计算)来查找键。

在这种情况下,毫无疑问,1.0、1和True具有不同的数据类型,并且也是不同的对象。

print(id(1), id(True), id(1.0))
print(type(1), type(True), type(1.0))

输出:
140407572928816 4308871808 140407573652336
<class 'int'> <class 'bool'> <class 'float'>

但事实上,它们共享相同的哈希值,因此字典将它们视为相同的键。

print(hash(1), hash(True), hash(1.0))

输出:
1 1 1

而且你是否注意到与 True 对应的值明明是 bool,但它打印的却是 float ?

输出:{'1': 'string', True: <class 'float'>}

这是因为,首先,将 True 作为键,其值为'bool'。接下来,当添加键 1 时,由于哈希值相同 Python 将其识别为同一个键。

因此,与 True 对应的值被 'int' 覆盖,而键(True)保持不变。

最后,当添加 1.0 时,又遇到了相同的情况,'int' 又被 'float' 覆盖。

总结

Python 中的字典功能强大且灵活,探索其各种特性和用例可以实现更高效和可读性更好的代码。

这篇文章探索了 Python 字典的一些有趣特性和用例。我们了解到,尽管列表不能直接作为字典的键,但通过扩展列表类并实现 __hash__ 方法,我们可以实现这一点。此外,我们还发现字典可以作为 IF 条件的替代方案,通过提供键来调用相应的函数。另外,我们介绍了将字典用于实现 switch 语句的方法,以及字典的 __missing__方法和 DefaultDict 的用法。最后,我们探讨了哈希等价键的现象。这些有趣的特性使得 Python 字典在编程中非常灵活和强大。无论是处理复杂的数据结构还是简化代码逻辑,我们都可以利用这些特性来提高代码的效率和可读性。