Reputation: 279
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
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
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
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
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
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