Reputation: 29099
I have created a custom class, and I want to use the **
operator on a instance for passing it to a function. I have already defined __getitem__
and __iter__
, but when I try f(**my_object)
, I'm getting
`TypeError: argument must be a mapping, not 'MyClass'`
What are the minimum required methods so that the custom class qualifies as a mapping?
Upvotes: 4
Views: 987
Reputation: 1123480
**
is not an operator, it is part of the call syntax:
If the syntax
**expression
appears in the function call, expression must evaluate to a mapping, the contents of which are treated as additional keyword arguments.
So if your class implements the Mapping
methods, then you should be good to go. You'll need more than just __getitem__
and __iter__
here.
A Mapping
is a Collection
, so must define at least __getitem__
, __iter__
, and __len__
; in addition most of __contains__
, keys
, items
, values
, get
, __eq__
, and __ne__
would be expected. If your custom class directly inherits from collections.abc.Mapping
, you only need to implement the first three.
Demo:
>>> from collections.abc import Mapping
>>> class DemoMapping(Mapping):
... def __init__(self, a=None, b=None, c=None):
... self.a, self.b, self.c = a, b, c
... def __len__(self): return 3
... def __getitem__(self, name): return vars(self)[name]
... def __iter__(self): return iter('abc')
...
>>> def foo(a, b, c):
... print(a, b, c)
...
>>> foo(**DemoMapping(42, 'spam', 'eggs'))
42 spam eggs
If you run this under a debugger, you'll see that Python calls the .keys()
method, which returns a dictionary view, which then delegates to the custom class __iter__
method when the view is iterated over. The values are then retrieved with a series of __getitem__
calls. So for your specific case, what was missing was the .keys()
method.
In addition, note that Python may enforce that the keys are strings!
>>> class Numeric(Mapping):
... def __getitem__(self, name): return {1: 42, 7: 'spam', 11: 'eggs'}[name]
... def __len__(self): return 3
... def __iter__(self): return iter((1, 7, 11))
...
>>> dict(Numeric())
{1: 42, 7: 'spam', 11: 'eggs'}
>>> def foo(**kwargs): print(kwargs)
...
>>> foo(**Numeric())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: foo() keywords must be strings
Upvotes: 6
Reputation: 799110
The first set of methods is used [...] to emulate a mapping...
It is also recommended that mappings provide the methods
keys()
,values()
,items()
,get()
,clear()
,setdefault()
,pop()
,popitem()
,copy()
, andupdate()
behaving similar to those for Python’s standard dictionary objects.It is recommended that [...] mappings [...] implement the
__contains__()
method to allow efficient use of the in operator...It is further recommended that [...] mappings [...] implement the
__iter__()
method to allow efficient iteration through the container
Upvotes: 1