作为**解包的映射的python类

时间:2011-12-22 08:39:49

标签: python class mapping argument-unpacking

如果没有子类化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。

3 个答案:

答案 0 :(得分:76)

__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'}

答案 1 :(得分:22)

如果您正在尝试创建映射 - 不仅仅满足传递给函数的要求 - 那么您真的应该继承自collections.Mapping。如documentation中所述,您需要实现:

__getitem__
__len__
__iter__

Mixin将为您实现其他所有功能:__contains__keysitemsvaluesget__eq__和{ {1}}。

答案 2 :(得分:1)

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

尝试使用带有 ** 的非映射对象时,会出现以下错误:

TypeError: 'Foo' object is not a mapping

如果我们在 CPython 的源代码中搜索该错误,我们可以找到 the code that causes that error to be raised

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 is actually dict_merge,当 dict_merge 返回负数时抛出错误。如果我们 check the source for 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()。