Class that acts as mapping for **unpacking

Without subclassing dict, what would a class need to be considered a mapping so that it can be passed to a method with **.

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

At least to the point where it throws errors of missing functionality of mapping, so I can begin implementing.

I reviewed emulating container types but simply defining magic methods has no effect, and using ABCMeta to override and register it as a dict validates assertions as subclass, but fails isinstance(o, dict). Ideally, I dont even want to use ABCMeta.

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

The __getitem__() and keys() methods will suffice:

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

Method 2

If you’re trying to create a Mapping — not just satisfy the requirements for passing to a function — then you really should inherit from collections.abc.Mapping. As described in the documentation, you need to implement just:

__getitem__
__len__
__iter__

The Mixin will implement everything else for you: __contains__, keys, items, values, get, __eq__, and __ne__.

Method 3

The answer can be found by digging through the source.

When attempting to use a non-mapping object with **, the following error is given:

TypeError: 'Foo' object is not a mapping

If we search CPython’s source for that error, we can find 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, and the error is thrown when dict_merge returns a negative number. If we check the source for dict_merge, we can see what leads to -1 being returned:

/* 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;

The key part being:

For the latter, we only require that PyMapping_Keys() and PyObject_GetItem() be supported.


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x