Hacker News new | past | comments | ask | show | jobs | submit login

It's most certainly not a mistake; Python 3 would probably have fixed it, if it were. It is an (admittedly, strange) side effect of the way 'def' works.



But 'def' doesn't have to work that way. Consider, in CL:

  > (defvar *fn*
      (let ((x 3))
        (lambda (&optional (y (list nil x)))
          (push 7 (car y))   ; modifies the list
          y)))
  *FN*
  > (funcall *fn*)
  ((7) 3)
  > (funcall *fn*)
  ((7) 3)
From this example you can see two things. First, the binding of 'x' is closed over when the lambda expression is evaluated. And second, the expression that provides the default value of 'y' is evaluated every time the function is called.

There's no fundamental reason it couldn't have worked that way in Python. (I understand that changing the language so it worked that way now would likely break some code.)

EDIT: fixed formatting.


My lisp is a bit rusty, but it looks like what you're doing there is returning a function which gets redefined every time you reuse the function.

The equivalent Python would be something like this:

    def function():
        x = 3
        def internal(x, foo=[]):
            foo.append([7])
            foo.append(x)
            return foo
        return internal(x)
        
    print function()
    print function()
Which does what you would expect:

    [[7], 3]
    [[7], 3]


No. In my example the function is created only once, and called twice.


What's that lambda thingo in the middle then? Pretty sure that's another function, redefined every time your function is called.


Yes, the lambda creates the function. Note that defvar does not. So there is still only one function being defined here.


Ok, I see now.

You've still got that &optional argument though. I don't see a huge amount of difference from a semantic point of view between that and the Python version though (ie. if x == None: ...).


No, the lambda expression creates the function, which is returned as the value of the let block; defvar just binds the function to a name so we can use it multiple times.


This translates into

    fn = lambda y=[y]: y.push(7); return y
if you accept the ; to separate statements, as the lambda in python is syntactically only allowed to contain one statement.

(The introduction of the variable x into the example is not important for the behavior of default arguments, however, it is important for a separate issue. I've stripped it out here.)



Surely not. The implementation already has to check that the number of provided arguments is valid. The decision of whether to evaluate the default expression can be part of that.

The code that evaluates the default expression doesn't need to be in a separate function, either, so the argument that calling that function is too expensive also doesn't hold water.

I just tried a test in SBCL:

  (defun foo1 (x) x)
  (defun test1 (n) (dotimes (i n) (foo1 (cons nil nil))))
  (time (test1 100000000))
  => 4.4 sec, or 44ns / iteration
  (defun foo2 (&optional (x (cons nil nil))) x)
  (defun test2 (n) (dotimes (i n) (foo2)))
  (time (test2 100000000))
  => 4.1 sec, or 41ns / iteration
The version with the optional parameter is actually slightly faster, which completely blows a hole in the performance argument.

Look, no language is perfect -- not even Common Lisp :-) I think users are better served when design flaws in a language are acknowledged without defensiveness than when bogus justifications are offered.


You're assuming that the function-calling overhead is the same in python as in CL. I don't think that's the case, and it definitely wasn't at the start.

I don't agree that this is a design flaw. As I recall it bit me once as a beginner, and never again in over a decade of using python, and as a lisp hacker you know you don't design a language for beginners. :-)


IMO mutable default arguments should be forbidden just as mutable keys are not accepted in dictionaries. All of the examples which claim to have a use-case for mutable default values can be rewritten with more explicit (thus more pythonic) constructs.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: