69

如果没有子类化 dict,一个类需要被视为一个映射,以便它可以传递给一个带有**.

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覆盖并将其注册为 dict 将断言验证为子类,但失败isinstance(o, dict)。理想情况下,我什至不想使用ABCMeta.

4

3 回答 3

94

和方法__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'}
于 2011-12-22T08:52:13.950 回答
32

如果你试图创建一个映射——不仅仅是满足传递给函数的要求——那么你真的应该从collections.abc.Mapping. 如文档中所述,您只需要实现:

__getitem__
__len__
__iter__

Mixin 将为您实现其他所有功能: __contains__, keys, items, values, get, __eq__, 和__ne__.

于 2011-12-23T08:37:12.457 回答
1

通过挖掘源代码可以找到答案。

尝试将非映射对象与 一起使用时**,会出现以下错误:

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_mergedict_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()。

于 2021-05-30T17:20:47.663 回答