3
votes

I have a class Commit.

class Commit:
    def __init__(self, uid, message):
        self.uid = uid
        self.message = message

    def __str__(self):
        print(self.__dict__)
        return textwrap.dedent('''\
        Commit: {uid}

        {message}
        ''').format(self.__dict__)

This seems right to me; both keys exists and are non-None, as seen from the output of the print call:

{'message': 'Hello, world!', 'uid': 1}

However, the call to str.format() on the list line raises a KeyError.

Traceback (most recent call last):
  File "../Pynewood/pnw", line 7, in 
    cli(sys.argv)
  File "/Users/daknok/Desktop/Pynewood/pynewood/cli.py", line 11, in cli
    print(commit)
  File "/Users/daknok/Desktop/Pynewood/pynewood/commit.py", line 14, in __str__
    ''').format(self.__dict__)
KeyError: 'uid'

Why am I getting this error, while the keys clearly exist in the dictionary?

2
You also can use vars(self). I think it looks nicer than self.__dict__John La Rooy

2 Answers

5
votes

str.format() expects kwargs, so you need to expand the dictionary using **.

def __str__(self):
    return textwrap.dedent('''\
    Commit: {uid}

    {message}
    ''').format(**self.__dict__)
3
votes

Radek Slupik is right, str.format expects individual keyword arguments, and in the code from the question the dict is simply passed as the first positional argument to format which would be extended on {0} in the format string. So str.format(**mapping) should be used.

But since Python 3.2 you can use str.format_map(mapping) instead. It works similarly to str.format(**mapping), but it doesn't turn the mapping into a dict. This allows us to give custom mapping classes to format, like in the example from the documentation:

>>> class Default(dict):
...     def __missing__(self, key):
...         return key
...
>>> '{name} was born in {country}'.format_map(Default(name='Guido'))
'Guido was born in country'

It also looks better and may give a tiny performance boost.