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
5
  • 2
    It seems that your problem is not with format() but with **. Commented Jul 24, 2019 at 11:13
  • but wenn I remove it and go for print(s.format(c)) it raises a KeyError Commented Jul 24, 2019 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. Commented Jul 24, 2019 at 11:21
  • yes using print(s.format(**c.__dict__)) does the trick. thx Commented Jul 24, 2019 at 11:29
  • Possible duplicate of How would I implement a dict with Abstract Base Classes in Python? Commented Jul 24, 2019 at 13:05

1 Answer 1

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__

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.