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.MutableMapping
1. 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
Access Item in a List of Lists
Python - Windows Shutdown Events
Where to Put Freeze_Support() in a Python Script
Regex Error - Nothing to Repeat
How to Write Inline If Statement for Print
How to Change Plot Background Color
Defining Private Module Functions in Python
Parameter Substitution for a SQLite "In" Clause
Error: 'Int' Object Is Not Subscriptable - Python
Dll Load Failed When Importing Pyqt5
Python 3.5 - "Geckodriver Executable Needs to Be in Path"
How to Make My Player Rotate Towards Mouse Position
Wrapping a C Library in Python: C, Cython or Ctypes
Board-Drawing Code to Move an Oval
Typeerror: Worker() Takes 0 Positional Arguments But 1 Was Given
Return String with First Match for a Regex, Handling Case Where There Is No Match
How to Add a Custom Ca Root Certificate to the Ca Store Used by Pip in Windows