Thursday, May 13, 2010

Python default argument madness

I've been learning python recently. I'm just getting started so I'm running into all the stupid beginner problems, for instance the insane behaviour of default argument values. Take this example:
from datetime import datetime
import time

class Weird:

 def __init__(self, dt = datetime.now()):
  self.dt = dt

w1 = Weird()
time.sleep(1)
w2 = Weird()
w3 = Weird(datetime.now())

print w1.dt == w2.dt # True
print w1.dt == w3.dt # False
The datetime in the w1 and w2 objects will actually be the same since the default argument value in the method definition is only evaluated once! What!?!? This essentially renders default arguments useless since you always end up writing the following:
class Weird:

 def __init__(self, dt = None):
  self.dt = dt or datetime.now()
I don't understand why you would design a default argument values features in your language like this. Doing it this way certainly violates the principle of least astonishment!

9 comments:

  1. Calling this "madness" is a bit extreme. Lazy callable code like this would be nifty, but I think the entire syntax would be polluted to make this work.

    If you think about the individual steps of what is happening, this is the only reasonable operation. Even though it isn't what you always want.

    ReplyDelete
  2. The only madness here is ignorance. Beginner or not, you should learn and fully understand the language before declaring it mad:

    http://magicrebirth.wordpress.com/2009/05/18/python-pitfall-passing-mutable-objects-as-default-args/

    ReplyDelete
  3. A good starting point for any Python beginner:

    http://zephyrfalcon.org/labs/python_pitfalls.html

    ReplyDelete
  4. No, its madness alright. The only reasonable operation? Pollute the syntax? These statements are not backed up with any explanation. It would read just the same, but be less astonishing/surprising/mad.

    ReplyDelete
  5. Indeed, calling it "madness" might be a bit strong, but using "Unexpected behaviour of Python default argument values" as a title sounds boring.

    Anyway, on a more serious note: You can't use 'beginner error' as an excuse for language quirks like this. As an experienced developer learning Python I try to keep an open mind and hope for the best, i.e. that Python is as good as I hope it is. So these kinds of things just make me thing 'urgh'.

    ReplyDelete
  6. Pete said: "If you think about the individual steps of what is happening, this is the only reasonable operation."

    Could you please elaborate. I can't think of a good reason why this should be the case. (Then again I'm just learning python).

    Also how would this pollute the syntax? How is are the following two essentially different? I dont know nothing about the python implementation, but I cant image the implementation being the problem.

    def foo1(x = []):
    ...

    def foo2():
    x = []
    ...

    Coming from Lisp I find this as annoying as scoping in python and the lack of proper lexcial closures...

    Maybe someone can shed some light on this design decision.

    P.S.: Btw for everyone that posted something like "The only madness here is ignorance.": In fact the only ignorance here is your argument...

    ReplyDelete
  7. My guess is that the fact default arguments behave in Python as they do has something to do with the fact that Python doesn't really have variable assignment, but rather has name binding. So I'm thinking the argument name gets bound to the default value at the moment the function is defined (i.e. when the function name gets bound to the function object). Once the argument name is bound to the default value it never gets reevaluated, since reevaluating a name binding doesn't really make sense.

    ReplyDelete
  8. (Sorry I didn't reply for so long, but I though I subscribed to the RSS feed. Apparently not...)

    Ok so you are saying the variable with default argument is actually in some kind of static scope in this function and gets rebound only when the value is supplied.

    I can only speculate here, but even if this was how it would be implemented, that not a reason that you would _have_ to do it this way. Take my example from above. I don't see why foo1 couldn't execute similar code to foo2. It would have to save the default argument expression as a code fragment and evaluate it every time the function is executed.

    Well I guess it is cumbersome for me to argue here about something that I know too little about. It just doesn't make proper sense for now. At least as long as you are aware of it its not too big a problem.

    ReplyDelete