Klohkwherk
Klohkwherk

Reputation: 279

String formatting in python

Recently I've been doing a lot of music based programming, and as such find myself doing this kind of thing a lot to deal with missing metadata in some songs:

default = {'title': 'Unknown title', 'artist': 'unknown Artist'}
default.update(song)
print '{title} - {artist}'.format(**default)

Is there a cleaner way to do this? I tried overriding __missing__ like so but missing keys still throw a KeyError:

class Song(dict):
    # dictionary with some nice stuff to make it nicer

    def __missing__(self, key):
        return 'Unknown {key}'.format(key = key)

Edit: Sorry I should have been clearer, basically the following must work.

s = Song()
print '{notAKey}'.format(s)

A couple of people have pointed out that the **s are not necessary.

Edit 2: I've "solved" my problem, at least to my satisfaction. It's debatable whether or not this is cleaner, but it works for me.

from string import Formatter

class DefaultFormatter(Formatter):

    def get_value(self, key, args, kwargs):
        # Try standard formatting, then return 'unknown key'

        try:
            return Formatter.get_value(self, key, args, kwargs)
        except KeyError:
            return kwargs.get(key, 'Unknown {0}'.format(key))

class Song(dict):

    def format(self, formatString):
        # Convenience method, create a DefaultFormatter and use it

        f = DefaultFormatter()
        return f.format(formatString, **self)

So the following will return 'Unknown notAKey'

k = Song()
print k.format('{notAKey}')

Upvotes: 5

Views: 4128

Answers (5)

Jared Ng
Jared Ng

Reputation: 5071

Warning: This solution does not actually work to solve the original question regarding '{title} - {artist}'.format(**default)

Have you tried importing defaultdict from collections?

from collections import defaultdict

class Song(defaultdict):
    def __missing__(self, key):
        return 'Unknown {key}'.format(key = key)

Here's a similar question: How to make a python dictionary that returns key for keys missing from the dictionary instead of raising KeyError?:

Upvotes: 3

immortal
immortal

Reputation: 3188

Well, one option I can think of is using a method that can inherently provide you with default values, namely getattr. If you'd transform your dict into an object turning keys into attributes, you could use the safe getattr to retreive values:

class AttributeDict(): 
    def __init__(self, source_dict):
        self._ATTRIBUTE_DICT_source_dict = source_dict
        for key in source_dict.keys():
            setattr(self, str(key), source_dict[key])

Now you can simply use:

song = AttributeDict(song)
artist = getattr(song, 'artist', 'Unknown Artist')

Upvotes: -1

Mike Graham
Mike Graham

Reputation: 76793

Unfortunately, when you call a method with the **foo syntax, it is converting foo just to a regular dict. The cleanest solution is probably the one you posted in your first snippet.

Upvotes: 1

David Heffernan
David Heffernan

Reputation: 613572

Executive summary: encapsulate the song in a class.

It sounds to me like a song is a primary data type in your code and is worthy of having a dedicated class to represent it. By all means use a dict internal to the class to store the data but try and move to a higher level of abstraction than a dict.

You should be aiming for this class to provide all the services needed to support the operations you perform on songs. House all the formatting code in the class so that it isn't scattered about your code that uses the song items. Indeed you will find all sorts of operations that want to become methods of this class.

As for the implementation of the formatting, it doesn't matter if it's a little long-winded since you write it in one place only.

Upvotes: 6

Mariy
Mariy

Reputation: 5924

I prefer Jared's version but if you really want to implement it yourself, here is one way:

>>> class Song(dict):
...     def __getitem__(self, key):
...             if key in self.keys(): return super(Song, self).__getitem__(key)
...             return 'Unknown {key}'.format(key=key)
... 
>>> s = Song()
>>> s['foo']
'Unknown foo'
>>> s['foo'] = 1
>>> s['foo']
1

Upvotes: -1

Related Questions