1

I have a class with __getitem__() function which is subscribable like a dictionary. However, when I try to pass it to a str.format() i get a TypeError. How can I use a class in python with the format() function?

>>> class C(object):
      id=int()
      name=str()

      def __init__(self, id, name):
        self.id=id
        self.name=name

      def __getitem__(self, key):
        return getattr(self, key)

>>> d=dict(id=1, name='xyz')
>>> c=C(id=1, name='xyz')
>>>
>>> #Subscription works for both objects
>>> print(d['id'])
1
>>> print(c['id'])
1
>>>
>>> s='{id} {name}'
>>> #format() only works on dict()
>>> print(s.format(**d))
1 xyz
>>> print(s.format(**c))
Traceback (most recent call last):
  File "<pyshell#13>", line 1, in <module>
    print(s.format(**c))
TypeError: format() argument after ** must be a mapping, not C
jan-seins
  • 1,253
  • 1
  • 18
  • 31
  • 2
    It seems that your problem is not with `format()` but with `**`. – quamrana Jul 24 '19 at 11:13
  • but wenn I remove it and go for `print(s.format(c))` it raises a KeyError – jan-seins Jul 24 '19 at 11:18
  • 1
    `c.__dict__` would get you inner dictionary. But I'd recommend adding some property to return it, rather than returning it directly. You can also make `C` inherit from `dict`, rather than `object`. – h4z3 Jul 24 '19 at 11:21
  • yes using `print(s.format(**c.__dict__))` does the trick. thx – jan-seins Jul 24 '19 at 11:29
  • Possible duplicate of [How would I implement a dict with Abstract Base Classes in Python?](https://stackoverflow.com/questions/21361106/how-would-i-implement-a-dict-with-abstract-base-classes-in-python) – quamrana Jul 24 '19 at 13:05

1 Answers1

4

As some of the comments mention you could inherit from dict, the reason it doesn't work is that:

If the syntax **expression appears in the function call, the expression must evaluate to a mapping, the contents of which are treated as additional keyword arguments. In the case of a keyword appearing in both expression and as an explicit keyword argument, a TypeError exception is raised.

For it to work you need to implement the Mapping ABC. Something along the lines of this:

from collections.abc import Mapping


class C(Mapping):

    id=int()
    name=str()

    def __init__(self, id, name):
        self.id = id
        self.name = name

    def __iter__(self):
        for x in self.__dict__.keys():
            yield x

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

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

This way you should just be able to use s = '{id}{name}'.format(**c) rather than s = '{id}{name}'.format(**c.__dict__)

You can also use MutableMapping from collections.abc module if you want to be able to change your class variables like in a dictionary. MutableMapping would also require the implementation of __setitem__ and __delitem__

Oliver Scott
  • 1,673
  • 8
  • 17