除了字符串和整数之外,Python 字典可以使用哪些键?

48

除了常见的字符串或整数外,有没有一些有趣的键的字典示例以及您如何在程序中使用它们?

我知道键所需要的仅是可哈希性(hashable),这意味着它必须是不可变的并且可比较(具有__eq__()__cmp__()方法)。

相关问题是:如何快速流畅地定义一个新的hashable


3
可哈希的(hashable)意味着你可以调用其__hash__方法并获得一个整数。没有更多的内容。如果字典/集合等是二叉树,就需要比较函数。 - user395760
我似乎被这个定义误导了:http://docs.python.org/glossary.html#term-hashable - Pete
2
@delnan:在Python的上下文中,可哈希也意味着可比较--请看Pete发布的链接。但它并不包括不可变。 - Sven Marnach
我正确地站着。现在你提到了它,它确实是必要的(因为字典使用比较进行冲突检测)。 - user395760
5个回答

42

让我们来尝试一些更加神秘的事情。假设您想要执行一个函数列表并存储每个函数的结果。对于抛出异常的每个函数,您都想记录异常,并计算每种异常被引发的次数。函数和异常都可以用作dict键,因此这很容易实现:

funclist = [foo, bar, baz, quux]

results    = {}
badfuncs   = {}
errorcount = {}

for f in funclist:
    try:
        results[f] = f()
    except Exception as e:
        badfuncs[f]   = e
        errorcount[type(e)] = errorcount[type(e)] + 1 if type(e) in errorcount else 1

现在你可以使用 if foo in badfuncs 来测试函数是否引发了异常(或使用 if foo in results 来查看其是否正常运行),if ValueError in errorcount 可以用来检查是否有任何函数引发了 ValueError,等等。


1
整洁,出色的例子,似乎是在内存中组织分析结果的好方法。修正错别字errorcount[t]-->errorcount[f]。计数似乎无效,不确定为什么在没有重新初始化字典的情况下再次迭代函数时不起作用。 - Pete
1
实际上,应该是 type(e) 而不是 t,因为这个字典应该计算每种异常类型被引发的次数。我最初在里面有一行代码说 t = type(e),所以才出现了 t。无论如何,已经修复好了。 :-) - kindall

18

你可以使用元组作为键,例如如果你希望创建一个多列索引。这里是一个简单的例子:

>>> index = {("John", "Smith", "1972/01/01"): 123, ("Bob", "Smith", "1972/01/02"): 124}
>>> index
{('Bob', 'Smith', '1972/01/02'): 124, ('John', 'Smith', '1972/01/01'): 123}
>>> index.keys()
[('Bob', 'Smith', '1972/01/02'), ('John', 'Smith', '1972/01/01')]
>>> index['John', 'Smith', '1972/01/01']
123

要使用字典作为键(可哈希的字典)的示例,请参见此答案: Python hashable dicts


2
请注意,您可以省略元组括号,从而提高代码的可读性 (index['John', 'Smith', '1972/01/01'])。 - Gabi Purcaru

16

您遗漏了对象成为 可哈希 最重要的方法: __hash__()

实现自己的可哈希类型的最短代码如下:

class A(object):
    pass

现在你可以使用 A 的实例作为字典的键:

d = {}
a = A()
b = A()
d[a] = 7
d[b] = 8

这是因为用户自定义类默认情况下可哈希,并且它们的哈希值是它们的id -- 所以只有当它们是同一对象时才会相等。

请注意,A的实例绝不是不可变的,并且尽管如此,它们仍然可以用作字典键。字典键必须是不可变的这种说法仅适用于内置类型。


同时可变和可哈希通常是允许的,但这通常是不明智的。 - user395760
4
许多种类的Python对象在技术上是可变的,但仍然是可哈希的。如果这是“疯狂”的话,为什么用户创建的对象默认情况下会是这样呢? - kindall
2
@kindall:我不太明白为什么object.__hash__会表现出这样的行为(因为使用它的对象作为字典键时无法按预期工作)。但是请参阅http://wiki.python.org/moin/DictionaryKeys,了解为什么这是一个巨大的头痛。 - user395760
不知道用户定义的类默认是可哈希的...感谢您的回答。 - Pete

6
请注意,我从未真正使用过这个,但我一直认为将元组用作键可以让您做一些有趣的事情。例如,我会发现这是一种方便的映射网格坐标的方式。你可以把它想象成一个视频游戏上的网格(也许是类似于火焰之纹章这样的策略游戏)。
>>> Terrain = { (1,3):"Forest", (1,5):"Water", (3,4):"Land" }
>>> print Terrain
{(1, 5): 'Water', (1, 3): 'Forest', (3, 4): 'Land'}
>>> print Terrain[(1,3)]
Forest
>>> print Terrain[(1,5)]
Water
>>> x = 3
>>> y = 4
>>> print Terrain[(x,y)]
Land

这样的东西。
编辑:正如Mark Rushakof在评论中指出的那样,我基本上是打算将其作为一个稀疏数组

我认为任何理智的人都会使用二维数组来存储地图的地形类型 :) - Sven Marnach
1
@Sven Marnach:我实际上有点喜欢元组,因为跳过空格(即留空)会更直观一些。 - eldarerathis
2
@eldarerathis 和 @Sven:是的,像这样的元组字典非常适合稀疏数组。 - Mark Rushakoff
1
@Sven Marnach:没关系。我认为这个数组为什么不能是稀疏数组,我并不明白。以《火焰之纹章》为例,如果我们考虑一个地图,其中大约90%的方块没有防御/回避修正(在FE中并不奇怪),那么对我来说使用稀疏数组似乎是很直观的选择。默认的方块类型(也许“陆地”对于这个问题来说是最好的)可以用于任何未在字典中定义的方块,这样可以节省一些内存。或者我完全错了,也有可能。 - eldarerathis
实际上,您甚至可以使用collections.defaultdict在使用未定义的键时返回默认值。 - Rob
显示剩余3条评论

2

我不知道为什么你想这样做(最好不要这样做)……但是除了字符串和整数,你也可以同时使用它们。作为一个初学者,我发现以下内容既强大又令人惊讶:

foo = { 1:'this', 2:'that', 'more':'other', 'less':'etc' }

这是一个完全有效的字典,可以轻松访问foo[2]foo['more']


根据我的经验,在使用Django时会遇到这个问题。查询集通常会返回整数(例如ID值)作为键,并且当转换为字典进行JSON表示时,它们看起来像这样。如果可能的话,我同意应该避免这种情况。 - medley56

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接