Dict-like类参数扩展
Dict-like类参数扩展
不使用继承dict的情况下,一个类需要满足什么条件才能被视为映射,从而可以传递给带有**
的方法。\n
from abc import ABCMeta class uobj: __metaclass__ = ABCMeta uobj.register(dict) def f(**k): return k o = uobj() f(**o) # 输出:f() argument after ** must be a mapping, not uobj
\n至少要达到抛出映射缺失功能错误的程度,这样我才能开始实现。\n我查看了模拟容器类型的方法,但仅仅定义魔法方法没有效果,并且使用ABCMeta
来重写并将其注册为dict验证了断言作为子类,但无法通过isinstance(o, dict)
。理想情况下,我甚至不想使用ABCMeta
。
问题的出现原因是在使用非映射对象与`**`结合时,会出现`TypeError`错误。在CPython的源代码中搜索该错误,可以找到引发该错误的代码。代码位于`ceval.c`文件的3362行处,具体内容如下:
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);
通过查看`dictobject.c`文件中的代码,可以发现`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()。
这是否意味着只需要定义`getitem`和`keys`方法就可以呢?
一年多过去了,所以我不记得之前都发现了什么,但是是的,这正是该注释所暗示的。这个发现也在顶部的回答中得到了验证。我发布这个答案是为了展示如何找到这样的问题。
Dict-like Class Argument Expansion这个问题的出现原因是在创建Mapping时,有时候只满足函数传递的要求是不够的。为了满足Mapping的要求,需要继承自collections.abc.Mapping,并实现__getitem__、__len__和__iter__这三个方法。但是实现这些方法之外,还需要实现其他一些方法,比如__contains__、keys、items、values、get、__eq__和__ne__。解决这个问题的方法是使用collections.abc.Mapping作为基类,并实现所需的方法。这样,Mixin会自动为我们实现其他的方法,大大减少了工作量。在Python的文档中有详细的说明,可以参考文档进行实现。另外,这个问题的解决方法已经被移动到了collections.abc包中的文档中,可以在文档中查看相关内容。
问题的出现原因是在函数f的调用中,参数以Dict-like类的实例作为输入,并且在函数内部使用**kwds的形式进行参数扩展。在这种情况下,函数f会调用Dict-like类的keys()方法和__getitem__()方法来获取参数的键和值,然后将其作为关键字参数传递给函数。
其中,Dict-like类的keys()方法返回一个包含键的列表,而__getitem__()方法根据给定的键返回相应的值。在这个例子中,Dict-like类D的keys()方法返回['a', 'b'],而__getitem__()方法将给定的键转换为大写字母。
解决这个问题的方法是在Dict-like类中实现keys()方法和__getitem__()方法,使其能够正确返回键和值。这样,在函数f中使用**kwds进行参数扩展时,就能够正确地获取参数的键和值,并将其作为关键字参数传递给函数。
以上是关于(Dict-like Class Argument Expansion)问题的原因和解决方法的内容。