我应该在什么情况下将元组用作字典键?

108
我正在学习Python中列表和元组的区别。一个明显的区别是,元组是不可变的(值在初始赋值后不能更改),而列表是可变的。
文章中的一句话让我困惑:
只有不可变元素才能用作字典键,因此只有元组而不是列表可以用作键。
我很难想象出什么情况下我会想要使用元组作为字典键。你能提供一个自然、高效、优雅或明显的解决方案的例子吗?
编辑:
感谢您的示例。到目前为止,我认为一个非常重要的应用是缓存函数值。

7
你可以使用元组,但只能使用其中具有不可变元素的元组。如果一个元组包含一个列表(作为其元素之一),则这样的元组不能用作键。基本规则是数据(元组)必须是可哈希的。 - pepr
13个回答

145

经典例子:您想将点的值存储为元组 (x, y)


7
哇,这太真实了。我想不出任何其他有效存储函数值的方式!如果您的函数计算非常耗费时间,您只需要计算一次并将点保存以便以后检索。+1!谢谢! - Escualo
7
同意。此外,任何你在处理内存中的内容时,如果在关系数据库中处理同一内容需要使用复合键(compound key),也要使用复合键。 - Joe Mabel
我现在正在实现完全相同的场景。检索的效率是多少?是O(1)还是O(N)? - shiv

39
salaries = {}
salaries[('John', 'Smith')] = 10000.0
salaries[('John', 'Parker')] = 99999.0

编辑 1 当然你可以这样做 salaries['John Smith'] = whatever,但是你需要额外的工作来将键分离成名字和姓氏。那么 pointColor[(x, y, z)] = "red" 呢?在这里元组键的好处更加突出。

我必须强调这不是最佳实践。在许多情况下,最好创建特殊类来处理这种情况,但Arrieta要求示例,我给了她(他)。

编辑 0

顺便说一下,每个元组元素也必须是可哈希的:

>>> d = {}
>>> t = (range(3), range(10, 13))
>>> d[t] = 11
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: list objects are unhashable
>>>

2
我不常用Python,但是salaries[('John Smith')] = 99998不是一个有效的字典键吗? - GrayWizardx
1
我同意你可以这样做,但我会用一个名为Employee的类来建模这些数据,包括__init__(self, Firs, Last, Salary)方法,并为列表中的每个元素创建一个实例。在这种情况下,使用“元组作为键”的技巧对我来说似乎有点不自然。你觉得呢? - Escualo
1
当然可以。但是如果您想将键分割为名和姓,那么您就需要额外的工作。 - Boris Gorelik
1
感谢您的编辑,我认为颜色示例非常好,就像答案中提到的一般dict[tuple] = f(tuple)。顺便说一下,我是“他”。 - Escualo
6
请注意,您不需要使用括号,元组是通过逗号定义的。 salaries['John','Smith']= 10000.0也可以运行 :) - Heretron
显示剩余3条评论

12

在机器学习和深度学习的背景下,如果你正在为最佳超参数进行超参数搜索,那么使用元组作为键绝对非常有用。

假设你正在搜索learning_rateregularization_factormodel_complexity的最佳超参数组合,则可以在Python中创建一个字典,使这些hparams可以采用不同的组合作为键,而其对应的训练算法的权重矩阵则作为值。

hparams_hist = {}
hparams_hist[(0.001, 0.7, 5)] = weight_matrix1
hparams_hist[(0.0001, 0.8, 2)] = weight_matrix2

这些权重矩阵需要进一步用于实时预测。


3
我发现我经常这样做!很好的例子。 - ruancomelli

9

我经常使用元组作为字典的键,例如:

  • I do use them when I have to create a unique key from multiple values e.g.

    based on first_name, last_name key could be key = '%s_%s'%(first_name, last_name) but better way is key = (first_name, last_name) because

    1. It is more readable, shorter and less computation
    2. It is easier to retrieve the individual values
    3. Most importantly key = '%s_%s'%(first_name, last_name) is wrong and may not give unique keys for all values of first_name and last_name e.g. when values contain _
  • Caching the results of a function

    def func(a1, b1):
        if (a1,b1) in cache: return cache[(a1,b1)]
        ...
    

6

我在一个比较网络设备地理位置的应用中使用了元组作为字典键。由于每个位置的设备名称都类似,这种方式可以自然地知道是否已经处理过多个匹配的设备。

例如:

seen = {}
seen[('abc', 'lax')] = 1
seen[('xyz', 'nyc')] = 1

6

当您想要展示一起成为键的多个元素时,使用元组作为键。

例如:{(<x坐标>,<y坐标>):<表示字母>}

在这里,如果我们单独使用 x-coordinatey-coordinate,我们将无法表示该点。


4
a[("John", "Doe")] = "123 Fake Street"

2

如果你正在构建一个基本的分析工具,你可以将其用于漏斗分析。

例如,统计有多少人在悬停在text2上后点击了image3。

    funnels = defaultdict(int)
    funnels[('hovered_text2', 'clicked_image3')] += 1

2

在排序的情况下,使用元组可能是有价值的。例如,假设字典键表示一个排序字段(显然需要一个默认排序字段来防止键为None)。如果您需要多个排序字段,例如按姓氏排序,然后按名字排序,那么将元组用作字典键是否是一个好主意呢?

当然,这样的想法可能有限的用途,但这并不意味着它完全没有用处。


2

您可以使用它来在搜索空间中进行近似恒定时间的点搜索。例如,您可以将其用于约束满足问题,其中每个元组可能包含一些约束条件。约束条件可能采用(v1.v2)的形式,其中对于着色问题,color(v1)!=color(v2)等等。 使用元组作为字典键,您将能够在常数时间内确定排列是否满足约束条件。


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