blue note
blue note

Reputation: 29099

python: applying ** operator on user-defined class

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

Answers (2)

Martijn Pieters
Martijn Pieters

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

Ignacio Vazquez-Abrams
Ignacio Vazquez-Abrams

Reputation: 799110

Emulating container types

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(), and update() 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

Related Questions