Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 747
blue_note Avatar asked Sep 02 '25 16:09

blue_note


2 Answers

** 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
like image 87
Martijn Pieters Avatar answered Sep 04 '25 18:09

Martijn Pieters


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

like image 22
Ignacio Vazquez-Abrams Avatar answered Sep 04 '25 18:09

Ignacio Vazquez-Abrams