1. 18

  2. 13

    I do wish that the Python community would take steps to fix some of the more confusing parts of the language.

    One of my long very term frustrations is how default parameter values for methods are treated in Python. They are created once when the method is declared and the value is shared among all calls of that method.

    Thus, this will not do what a newbie expects.

    def wrap(v,x=[]):
           return x
    > wrap(1)
    > wrap(10)
    [1, 10]

    This behavior is nonintuitive, and removes the utility of default parameters for anything other than immutable primitive types.

    1. 6

      Cool, I didn’t know Python had C’s function-local static variables! That sounds great, I’ll be using those everywhere! :-D

      1. 3

        Ah, Ahhhh, Ahhhhhhhhhh….. :D

      2. 4

        I wouldn’t consider myself a newbie and wow… that is totally not what I expected. But it does make sense.

        1. 1

          Why do you say it makes sense? Surely the concept of a shared variable is orthogonal to that of default parameter values?

          1. 6

            It makes sense because it’s consistent with behaviour of all other values. Don’t get me wrong, I’m not fond of the “everything is mutable” approach, but at least it’s consistent. If it’s mutable, it’s mutable, regardless of context. Not “sometimes we’ll copy/deepcopy (which one?) it for you, but other times you need to remember to do it yourself”.

            That’s a funny implication of course: consistency and intuitivity can be contradictory goals.

            1. 5

              I do not see how it is related to mutability. If it was, then the following i.e.

              x = []
              y = x.append(1)
              z = []

              Should have resulted in printing [1,2] isn’t it? (i.e the object represented by [] is mutated in the second line.)

              The problem is that most of us mentally translate the default parameter value syntax as a guarded assignment within the body of the function. Unfortunately, Python translates the default parameter value assignment as occurring during the def statement. i.e

              class X():
                  def __init__(self):
              def x(x=X()):

              Prints 2. I do not see why that is intuitive or justified.

              (edited to add the last portion).

            2. 2

              Python is pass-by-reference, so the default parameter is a reference to that specific list. (That’s my understanding, anyway.)

              1. 1

                Python’s behavior is neither truly pass-by-reference nor pass-by-value – Python sometimes will behave in ways that look like either – and it’s not super helpful to try to make it fit one or the other.

                Some people have suggested the unwieldy term “pass-by-object-reference” to describe how Python (and some other languages) behave, but honestly people mostly want these terms to describe the behavior of languages that expose and allow you to manipulate memory in a much lower-level way than Python does.

          2. 3

            This is one of the cliché things that gets brought up every time someone wants to criticize Python (see also “len() shouldn’t be a function”, “why do we have to explicitly accept self”, etc.). But I never really got it.

            The fact that there’s always a reply along the lines of “wow, I’ve been doing Python for years and never knew that” (as there is here!) suggests that it’s not as common to run into as critics seem to want to imply. And there’s no obvious objectively correct answer to how it should work; even if you’re teaching a complete beginner you can ask them to think about what a default argument value means, and likely end up getting arguments for either approach.

            1. 4

              I run into it frequently in the sense that my code is littered with stuff like

              def f(x = None):
                  if x is None:
                      x = []

              where I mean but cannot actually write

              def f(x = [])
            2. 3

              I just want to agenda-free mention that the book Fluent Python does a great job of covering those areas of Python!

              1. 2

                Seconded! I just wish that there was a book of that quality for every programming language.

              2. 1

                I agree I was very upset at first when I found a bug caused by this behavior in my code.

                If this behaviour seems counter intuitive to you with an empty list or an empty dict default parameter, how should it work with any other complex type ?

                def display(row = Row(val1, val2)):

                It starts to get ugly if we follow this path. Functions signature should be descriptive and should not contains logic somehow.

                Besides in python, functions signature are evaluated once when its module is imported. Knowing that, instanciating an object as a default parameter must yield the same reference for every call to this function.

                Moreover, using type hints with None as the defacto default parameter make your intention clearer and more consistent.

                def display(row: Optional[Row] = None):
                    row = row or Row("default value")

                My answer is not well organized, but I hope my point is clear :)

                1. 1
                   def display(row = None):
                       row = row or Row("default value")

                  This should have been the default parameter value semantics. I do not see why the parameter has to be instantiated at the definition time rather than at the call time.

              3. 5

                I think it’s worth adding the currently top-voted comment on the HN thread about this, because I have read a couple of posts about 3.8 and this wasn’t mentioned (or I missed it):

                To me, the headline feature for Python 3.8 is shared memory for multiprocessing (contributed by Davin Potts).

                Some kinds of data can be passed back and forth between processes with near zero overhead (no pickling, sockets, or unpickling).

                This significantly improves Python’s story for taking advantage of multiple cores.

                1. 1

                  Didn’t Python already have mmap support?

                  1. 1

                    Yes, someone in the linked comment chain also mentions mmap, and provides some details. Seems like this makes it easier to use, at least.