作为解包映射的类

78 投票
4 回答
14112 浏览
提问于 2025-04-17 08:50

如果不想通过继承字典(dict)来创建一个类,那么这个类需要具备什么特性,才能被认为是一个映射(mapping),以便可以用在带有**的函数中呢?

from abc import ABCMeta

class uobj:
    __metaclass__ = ABCMeta

uobj.register(dict)

def f(**k): return k

o = uobj()
f(**o)

# outputs: f() argument after ** must be a mapping, not uobj

至少要做到能抛出缺少映射功能的错误,这样我才能开始实现相关功能。

我查看了模拟容器类型的方法,但仅仅定义一些特殊方法并没有效果。而使用ABCMeta来重写并注册为字典,虽然可以通过子类的验证,但却不能通过isinstance(o, dict)的检查。理想情况下,我甚至不想使用ABCMeta

4 个回答

3

答案可以通过查看源代码找到。

当你尝试用一个不是映射对象的东西去使用 ** 时,会出现以下错误:

TypeError: 'Foo' object is not a mapping

如果我们在CPython的源代码中搜索这个错误,就能找到导致这个错误的代码

case TARGET(DICT_UPDATE): {
    PyObject *update = POP();
    PyObject *dict = PEEK(oparg);
    if (PyDict_Update(dict, update) < 0) {
        if (_PyErr_ExceptionMatches(tstate, PyExc_AttributeError)) {
            _PyErr_Format(tstate, PyExc_TypeError,
                            "'%.200s' object is not a mapping",
                            Py_TYPE(update)->tp_name);

PyDict_Update 实际上是 dict_merge,当 dict_merge 返回一个负数时,就会抛出这个错误。如果我们查看 dict_merge 的源代码,就能看到导致返回 -1 的原因:

/* We accept for the argument either a concrete dictionary object,
 * or an abstract "mapping" object.  For the former, we can do
 * things quite efficiently.  For the latter, we only require that
 * PyMapping_Keys() and PyObject_GetItem() be supported.
 */
if (a == NULL || !PyDict_Check(a) || b == NULL) {
    PyErr_BadInternalCall();
    return -1;

关键部分是:

对于后者,我们只需要支持 PyMapping_Keys() 和 PyObject_GetItem()。

34

如果你想创建一个映射(Mapping),而不仅仅是为了满足传递给函数的要求,那么你真的应该从 collections.abc.Mapping 这个类继承。根据文档的描述,你只需要实现以下内容:

__getitem__
__len__
__iter__

这个混合类(Mixin)会为你实现其他所有功能,比如 __contains__keysitemsvaluesget__eq____ne__

104

这段话的意思是,使用 __getitem__()keys() 这两个方法就可以满足需求了。

>>> class D:
        def keys(self):
            return ['a', 'b']
        def __getitem__(self, key):
            return key.upper()


>>> def f(**kwds):
        print kwds


>>> f(**D())
{'a': 'A', 'b': 'B'}

撰写回答