Class That Acts as Mapping for **Unpacking

Class that acts as mapping for **unpacking

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

Unpacking a class

As pointed out in the comments, writing a conformant subclass of the collections.abc.Mapping abstract class is the way to go. To (concretely) subclass this class, you need to implement __getitem__, __len__, and __iter__ to behave consistently like a dictionary would. So that means __getitem__ expects a string, __iter__ returns an iterable of strings, etc.

For a simple example, we'll simply delegate all of these to self.__dict__, but in real code you'd likely want to do something more refined.

from collections.abc import Mapping

class FooClass(Mapping):

def __init__(self, a, b):
self.a = a
self.b = b

def __getitem__(self, x):
return self.__dict__[x]

def __iter__(self):
return iter(self.__dict__)

def __len__(self):
return len(self.__dict__)

def bar(a, b):
return a + b

foo = FooClass(40, 2)
print(bar(**foo))

How to define self-made object that can be unpacked by `**`?

Any mapping can be used. I'd advise that you inherit from collections.Mapping or collections.MutableMapping1. They're abstract base classes -- you supply a couple methods and the base class fills in the rest.

Here's an example of a "frozendict" that you could use:

from collections import Mapping

class FrozenDict(Mapping):
"""Immutable dictionary.

Abstract methods required by Mapping are
1. `__getitem__`
2. `__iter__`
3. `__len__`
"""

def __init__(self, *args, **kwargs):
self._data = dict(*args, **kwargs)

def __getitem__(self, key):
return self._data[key]

def __iter__(self):
return iter(self._data)

def __len__(self):
return len(self._data)

And usage is just:

def printer(**kwargs):
print(kwargs)

d = FrozenDict({'a': 1, 'b': 2})
printer(**d)

To answer your question about which "magic" methods are necessary to allow unpacking -- just based on experimentation alone -- in Cpython a class with __getitem__ and keys is enough to allow it to be unpacked with **. With that said, there is no guarantee that works on other implementations (or future versions of CPython). To get the guarantee, you need to implement the full mapping interface (usually with the help of a base class as I've used above).

In python2.x, there's also UserDict.UserDict which can be accessed in python3.x as collections.UserDict -- However if you're going to use this one, you can frequently just subclass from dict.

1Note that as of Python3.3, those classes were moved to thecollections.abc module.

Is Python dictionary unpacking customizable?

I've managed to satisfy the constraint with two methods (Python 3.9) __getitem__ and keys():

class Foo:
def __getitem__(self, k): # <-- obviously, this is dummy implementation
if k == "a":
return 1

if k == "b":
return 2

def keys(self):
return ("a", "b")

foo = Foo()
assert {"a": 1, "b": 2} == {**foo}

For more complete solution you can subclass from collections.abc.Mapping (Needs implementing 3 methods __getitem__, __iter__, __len__)

python: Overloading ** dict unpacking

Solution due to @peter

class M:
# ... __getitem__ and other functions
def keys(self):
k = self.to_dict().keys()
return k

Why this unpacking of arguments does not work?

The ** syntax requires a mapping (such as a dictionary); each key-value pair in the mapping becomes a keyword argument.

Your generate() function, on the other hand, returns a tuple, not a dictionary. You can pass in a tuple as separate arguments with similar syntax, using just one asterisk:

create_character = player.Create(*generate_player.generate())

Alternatively, fix your generate() function to return a dictionary:

def generate():
print "Name:"
name = prompt.get_name()
print "Age:"
age = prompt.get_age()
print "Gender M/F:"
gender = prompt.get_gender()

return {'name': name, 'age': age, 'gender': gender}


Related Topics



Leave a reply



Submit