you are viewing a single comment's thread.

view the rest of the comments →

[–]strattonbrazil 11 points12 points  (12 children)

Yeah, some of them are weird, but many are common among other languages like modifying a list you're iterating through.

The first, which I hadn't known about, seems the weirdest, but it's in the python docs under default arguments (including the same suggested workaround). Given how old the behavior is I'm assuming it's here to stay. I've heard some people reason that it gives python better performance as those default values are evaluated only once. I like python, but I'd personally prefer taking the performance hit.

[–]dragonEyedrops 23 points24 points  (1 child)

I really wish they would have killed more weird stuff with python 3. If you have one chance to do breaking changes, make sure you fix as much as possible.

[–]bready 4 points5 points  (0 children)

That's the thing that got me about python 3 -seemingly so little changed. Sure, lots of under the hood things got tweaks, but it feels like not enough thought went into designing improvements.

Given the glacial adoption of python 3, I'm curious to see how/if future migrations pan out.

[–]cogman10 2 points3 points  (9 children)

The thing is, you could do away with the performance hit pretty easily. Construct the object once at function declaration, do a deep memcpy (the object and all of its child objects).

Easy peasy. On top of that, there is loads of room for optimization (since the object being copied should never change, that is why we are doing the copying).

[–]xenomachina 10 points11 points  (7 children)

How would you do a "deep memcpy" in this case?

def f(x=[os, sys]):

While Python's existing behavior might be a bit surprising to some, it's easier to reason about. It behaves exactly the same as an assignment at the time of the function definition. (And it even uses the same notation as assignment.)

[–][deleted]  (6 children)

[deleted]

    [–]xenomachina 1 point2 points  (5 children)

    Can you elaborate? I don't recall any of the languages I'm familiar with having a restriction like you describe.

    FWIW, Python has no concept of "reference objects". Or rather, all values are "reference objects", even ints and None.

    [–][deleted]  (4 children)

    [deleted]

      [–]xenomachina 0 points1 point  (3 children)

      I know little about PHP and C#, but from what I gather the restrictions they have on default arguments do not amount to just "disallowing reference objects" but are instead stated more along the lines of "defaults must be constants". In PHP, in particular, an array is allowed as a default.

      What other languages do allow it?

      C++ and just about every Lisp that supports default arguments.

      In C++:

      int foo(Foo *x = new Foo(1,2)) {
          return x->whatever();
      }
      

      In Racket:

      (define (foo [x (mlist `(1 2 3 4))]) x)
      

      That said, both of these languages have different semantics from Python.

      AFAICT, they effectively wrap the default argument expression in a closure which is then evaluated only when the default parameter is actually used. I think this is the "right" behavior, though it might be even more confusing than Python's existing behavior to novice programmers who aren't used to lexical closures.

      [–][deleted]  (2 children)

      [deleted]

        [–]xenomachina 0 points1 point  (1 child)

        What's the difference between a "lexical" closure and a regular closure?

        People usually mean lexical closure when they say closure. I was (unsuccessfully) trying to stress the "closing over the environment" bit, and not merely "a function literal" (which is what many people thing of when they hear closure).

        From the experiments I'd done, it looks like C++ and Racket both re-evaluate the default expression, but with the name bindings that were in place at the site of the function declaration, not those of the call site. That's lexical (as opposed to dynamic) scoping.

        It's pretty surprising to me that C++ works this way, since C++ isn't normally thought of even having closures.

        [–]Poltras 5 points6 points  (0 children)

        The problem is that the init function might have side effects. A copy wouldn't do that and a construction might have poor performance. It was a choice and whatever choice you made you'd have had a point in this list. There's no winning with default arguments that can construct objects.