如果密钥不在 some_dict 中,为什么 some_dict['key'] = somevalue 有效?

Why does some_dict['key'] = somevalue work if the key is not in some_dict?

我知道我可以通过

在 python 字典中添加一个新的 key/value
some_dict['absent_key'] = somevalue

但我不太了解内部结构。

我曾经认为字典的行为类似于 C++ 映射。 [] 运算符将为给定键创建元素(如果它不存在),然后 return 对它的引用,以便可以在与运算符 [=15 相同的行中为它分配一个值=].

但 C++ 中的这种行为会导致如果我们从映射中查询不存在的键的值,则会为该键创建元素,并且值类型的默认值为 returned 而不是错误。在 python 中,这会抛出一个 KeyError.

所以我不明白的是:既然 [] 运算符也必须在 python 中的 = 之前进行评估(我认为?),它的行为是否会有所不同取决于如果结果将被读取或分配一个值(在表达式评估的那个点它不应该知道)?

python 计算表达式的顺序有区别吗?或者解释器只是更聪明,因为字典具有硬编码类型,所以它更准确地知道它的行为方式,而 std::map 在 'library' 中所以编译器可以假设更少?还是其他原因?

操作:

some_dict[key]

some_dict[key] = value

del some_dict[key]

使用对象的不同特殊方法:__getitem__, __setitem__ and __delitem__。因此,实现它们的不仅仅是一个运算符 ([])。

或许举个例子可以说明:

class Something(dict):  # subclassing dict
    def __getitem__(self, key):
        print('trying to get', key)
        return super().__getitem__(key)
    def __setitem__(self, key, value):
        print('trying to set', key, 'to', value)
        return super().__setitem__(key, value)
    def __delitem__(self, key):
        print('trying to delete', key)
        return super().__delitem__(key)

测试:

>>> s = Something({'a': 1, 'b': 2})
>>> s['a']
trying to get a
1

>>> s['c'] = 10
trying to set c to 10

>>> del s['b']
trying to delete b

所以这取决于它们是如何实现的。在普通的 Python dicts __getitem__ 中只是 returns 键的值或者如果它不存在则抛出。

但是子类也可以实现 __missing__ 方法 - 如果字典中不存在键(在查找期间),他们想要自定义行为。

幕后发生了什么?

在 Python 中,当您为键赋值时:

dictionary[key] = value

Python将上面的语法糖翻译成:

dictionary.__setitem__(key, value)

如您所见,在幕后 Python 调用了 __setitem__ 方法。 __setitem__ 方法直接对应于索引数据结构并为所述索引分配新值的操作。它可以被重载以自定义它的行为。

对于 Python 字典,__setitem__ 的默认行为是更改键值(如果存在),如果不存在则引发 KeyError。为了证明这一点,您可以子class dict class 并重载 __setitem__ 以显示它的参数:

>>> class Dict(dict):
...     def __setitem__(self, key, value):
...         print('Putting "%s" in dict with value of "%s"' % (key, value))
...         super().__setitem__(key, value)
...
>>>
>>> d = Dict()
>>> d['name'] = 'Hammy'
Putting "name" in dict with value of "Hammy"
>>> d['age'] = 25
Putting "age" in dict with value of "25"
>>> d
{'name': 'Hammy', 'age': 25}

Python 是否有 std::map 等价物?

就像@MSeifert 所说的那样,您可以通过重载 __missing__ 方法自定义键不存在时发生的情况。

这就是来自CPython的collections.defaultdict class does in the standard library. It overloads __missing__ to create a missing key and map a default value of your choice to it. Here's the relevant snippet来源:

static PyObject *
defdict_missing(defdictobject *dd, PyObject *key)
{
    PyObject *factory = dd->default_factory;
    PyObject *value;
    /* ... */
    value = PyEval_CallObject(factory, NULL);
    if (value == NULL)
        return value;
    if (PyObject_SetItem((PyObject *)dd, key, value) < 0) {
        Py_DECREF(value);
        return NULL;
    }
    return value;
}

注意 defaultdict 是用 C 语言实现的。下面是一个用法示例:

>>> from collections import defaultdict
>>> map = defaultdict(int)
>>> map['a'] = 1
>>> map['b'] = 2
>>> map['c'] # default factory function `int` called
0
>>> map
defaultdict(<class 'int'>, {'a': 1, 'b': 2, 'c': 0})

defaultdict 与 std::map::operator[] 的行为非常匹配。如果在使用 std::map::operator[] 时键不存在,运算符会调用与键值的预期类型相匹配的 "factory function",并将其分配给缺失的键。

因此,如果您想要类似 std::map 的功能,请使用 defaultdict。不过请注意,我说的是 "like"。那是因为 C++ 和 Python 是 两种完全不同的语言 。说一种语言中的数据结构在另一种语言中具有 精确 等价物的说法很少是正确的。

my_dict['key'] = 'value' 表示法只是糖分:

my_dict.__setitem__('key', 'value')

该函数完成所有存储数据的工作。它可以实现,但是,你想要。 python 解释器和库使用的底层机制通常来自更快的编译语言,如 C。

还有更多像这样的函数,例如 __len__()__getitem__(x)__delitem__(x) 可以处理所有 the other dict 类操作。