为什么foo['bar': 'baz']引发TypeError而不是SyntaxError?

42 投票
3 回答
2101 浏览
提问于 2025-04-16 06:55

这是一个纯粹出于好奇心的问题。这里的语法显然是无效的:

foo = {}
foo['bar': 'baz']

很明显发生了什么,开发者把一行代码从字典的定义中移出来了,但没有把它从字典的字面量声明改成赋值语法(因此受到了适当的嘲笑)。

但我想问的是,为什么在这里Python会抛出TypeError: unhashable type而不是SyntaxError?它试图哈希的是什么类型?仅仅这样做:

'bar': 'baz'

会导致SyntaxError,就像这样:

['bar': 'baz']

所以我看不出是什么类型导致了无法哈希的问题。

3 个回答

1

表达式 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 前提是 startstopstep 都是可以哈希的。

22

我想给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的回答 :)

65

在索引操作中使用冒号,会生成一个叫做 slice 的对象,而这个对象是不能被哈希的。

撰写回答