类,该类充当 * * 解包的映射

如果没有对 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

12056 次浏览

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

如果您试图创建一个 Mapping ーー而不仅仅是满足传递给函数的要求ーー那么您确实应该从 collections.abc.Mapping继承。正如在 文件中所描述的,您只需要实现:

__getitem__
__len__
__iter__

Mixin 将为您实现所有其他功能: __contains__keysitemsvaluesget__eq____ne__

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

当试图在 **中使用非映射对象时,会出现以下错误:

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