如果没有子类化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。
答案 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__
,keys
,items
,values
,get
,__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()。