为什么foo['bar': 'baz']引发TypeError而不是SyntaxError?
这是一个纯粹出于好奇心的问题。这里的语法显然是无效的:
foo = {}
foo['bar': 'baz']
很明显发生了什么,开发者把一行代码从字典的定义中移出来了,但没有把它从字典的字面量声明改成赋值语法(因此受到了适当的嘲笑)。
但我想问的是,为什么在这里Python会抛出TypeError: unhashable type
而不是SyntaxError
?它试图哈希的是什么类型?仅仅这样做:
'bar': 'baz'
会导致SyntaxError
,就像这样:
['bar': 'baz']
所以我看不出是什么类型导致了无法哈希的问题。
3 个回答
表达式 foo[a:b]
其实是 foo[slice(a, b)]
的一种简化写法。也就是说,你是在用一个 slice
对象来索引字典。这种写法对 foo
的类型没有限制。
在 Python 3.11 及之前的版本中,这样做会引发一个 TypeError
错误,因为 slice 对象不能被 哈希。不过,从 Python 3.12 开始(发布于2023-10-02),slice 对象现在可以被哈希1,所以可以作为字典的键了。
原问题中的代码现在会引发 KeyError
错误,而不是 TypeError
:
>>> foo = {}
>>> foo['bar': 'baz']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: slice('bar', 'baz', None)
现在你也可以写
foo = {}
foo["bar" : "baz"] = 1
这会生成字典 {slice('bar', 'baz', None): 1}
。
1 前提是 start
、stop
和 step
都是可以哈希的。
我想给Ignacio的回答补充一些细节(他的回答很棒),这让我花了一些时间去理解,对于像我这样没搞明白的人(可能只有我一个人没懂,因为我没看到其他人问这个问题,但谁知道呢 :)):
第一次我在想,什么是切片?字典索引不接受切片吗?
但这其实是我愚蠢的问题,因为我忘了Python是动态的(我真傻)。所以当Python第一次编译代码时,它并不知道foo
是字典还是列表,因此它会把像foo['foo':'bar']
这样的表达式当作切片来处理。要验证这一点,你可以这样做:
def f():
foo = {}
foo['bar':'foo']
使用dis模块,你会看到表达式'bar':'foo'
已经自动转换成了切片:
dis.dis(f)
2 0 BUILD_MAP 0
3 STORE_FAST 0 (foo)
3 6 LOAD_FAST 0 (foo)
9 LOAD_CONST 1 ('bar')
12 LOAD_CONST 2 ('foo')
15 SLICE+3 <<<<<<<<<<<<<<<<<<<<<< HERE!!!!!!
16 POP_TOP
17 LOAD_CONST 0 (None)
20 RETURN_VALUE
一开始我承认我没有想到这一点,直接去查Python的源代码,试图理解为什么,因为列表的__getitems__
和字典的__getitem__
不一样。但现在我明白了,因为如果它是切片,而切片是不可哈希的,那么就应该抛出unhashable type
的错误。所以这是字典的__getitem__
代码:
static PyObject *
dict_subscript(PyDictObject *mp, register PyObject *key)
{
PyObject *v;
long hash;
PyDictEntry *ep;
assert(mp->ma_table != NULL);
if (!PyString_CheckExact(key) || // if check it's not a string
(hash = ((PyStringObject *) key)->ob_shash) == -1) {
hash = PyObject_Hash(key); // check if key (sliceobject) is hashable which is false
if (hash == -1)
return NULL;
}
....
希望这能帮助像我一样的人理解Ignacio的精彩回答,抱歉如果我只是重复了Ignacio的回答 :)
在索引操作中使用冒号,会生成一个叫做 slice
的对象,而这个对象是不能被哈希的。