Default arguments in Python: two easy blunders
I’m glad I stumbled across Patrick Altman’s tweet about a “default bug in Django“. I’d never have guessed you can pass a callable to a field’s default= argument, otherwise. That’s quite a powerful idiom, and I think I’ll use it a lot.
To balance the karma, I’d like to post a quick reminder to everyone else that expressions in default arguments are calculated when the function is defined, not when it’s called. In Patrick’s code, for example, all objects created in the same running session got the same timestamp. Try this in the Python interactive prompt:
>>>import time >>> def report(when=time.time()): … print when … >>> report() 1210294387.19 >>> time.sleep(5) >>> report() 1210294387.19
Until the interpreter quits, you’ll always get the same timestamp. The correct way to go about this is to default to None or some other sentinel, then replace it inside the function:
>>> def report(when=None): … if when is None: … when = time.time() … print when … >>> report() 1210294762.29 >>> time.sleep(5) >>> report() 1210294772.23
Now that you know about that blunder, you should be able to figure out what’s going on with this second classic blunder when using default arguments in Python:
>>> def spam(eggs=[]): … eggs.append(”spam”) … return eggs … >>> spam() ['spam'] >>> spam() ['spam', 'spam'] >>> spam() ['spam', 'spam', 'spam'] >>> spam() ['spam', 'spam', 'spam', 'spam']
Django vs feedparser on dates
I'm having trouble storing feedparser results in a Django model.
It's all about timestamps. Feedparser returns timestamps in a standard time nine-tuple, asserting UTC. Django wants datetime objects. So, I'm trying to translate:
django_timestamp = datetime.datetime.fromtimestamp(time.mktime(feedparser_timestamp))
feedparser_timestamp = django_timestamp.utctimetuple()
This works fine for the majority of timestamps, but sometimes translating to datetime and back mutates the timestamp. In turn, that makes get-if-modified-since somewhat unreliable. Here are some examples, from my log file:
WARNING: (2004, 11, 19, 5, 13, 31, 4, 324, 0) => datetime.datetime(2004, 11, 19, 6, 13, 31) => (2004, 11, 19, 6, 13, 31, 4, 324, 0)
WARNING: (2005, 11, 2, 2, 17, 55, 2, 306, 0) => datetime.datetime(2005, 11, 2, 3, 17, 55) => (2005, 11, 2, 3, 17, 55, 2, 306, 0)
WARNING: (2006, 12, 13, 0, 21, 25, 2, 347, 0) => datetime.datetime(2006, 12, 13, 1, 21, 25) => (2006, 12, 13, 1, 21, 25, 2, 347, 0)
WARNING: (2004, 11, 14, 23, 55, 31, 6, 319, 0) => datetime.datetime(2004, 11, 15, 0, 55, 31) => (2004, 11, 15, 0, 55, 31, 0, 320, 0)
I'm off by an hour. I smell a problem with daylight savings. I just wish I knew what to do about it.
I've waved a dead chicken at this one all the ways I know how. Every change I make breaks the conversion entirely. So, I'm throwing this out to the community in the hope that someone can help me.
Django me again, baby!
I know, I know: I haven’t written about Django for a while. Nor am I this time, really, except to note that I’m still a Subversion novice and have screwed up my vendor branch strategy. I’ll get back to it, though, honest. All I need is some Copious Spare Time…
In other news: if you subscribe to this feed, and you noticed some errors, and now you’re seeing this: I must have fixed it, eh? ;)