作为解包映射的类
如果不想通过继承字典(dict)来创建一个类,那么这个类需要具备什么特性,才能被认为是一个映射(mapping),以便可以用在带有**
的函数中呢?
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
来重写并注册为字典,虽然可以通过子类的验证,但却不能通过isinstance(o, dict)
的检查。理想情况下,我甚至不想使用ABCMeta
。
4 个回答
3
答案可以通过查看源代码找到。
当你尝试用一个不是映射对象的东西去使用 **
时,会出现以下错误:
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()。
34
如果你想创建一个映射(Mapping),而不仅仅是为了满足传递给函数的要求,那么你真的应该从 collections.abc.Mapping
这个类继承。根据文档的描述,你只需要实现以下内容:
__getitem__
__len__
__iter__
这个混合类(Mixin)会为你实现其他所有功能,比如 __contains__
、keys
、items
、values
、get
、__eq__
和 __ne__
。
104
这段话的意思是,使用 __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'}