Hacker News new | ask | show | jobs
by eiopa 3826 days ago
This kills it for me too. I rely on pypy, and I just can't see myself sacrificing my productivity by using Six.

Also, Python 3 neglected to fix one of the most annoying things about the language - default arg value

  def foo(x=[]):
      x.append(1)
      print x
  
  foo() # 1
  foo() # 1 1
Why is this still busted??
4 comments

Are you referring persistence of mutable default args? Which then potentially leads to bugs when you treat it as a new variable on each function call?

This is intentional behavior. It's the result of one-time evaluation of default args, which is important for memoization. It's also really useful in combination with late-binding closures. For example, using a lambda as a generator function:

    def create_multipliers():
        return [lambda x : i * x for i in range(5)]
This doesn't work, you'll get all 8's. Instead you need to:

    def create_multipliers():
        return [lambda x, i=i : i * x for i in range(5)]
Late-binding closures and memoization strategies are pretty core language features; I wouldn't expect them to change (and many python devs would be pretty pissed if they did). Yes, this can be confusing with mutable default objects, but the alternative is to have disparate behavior depending on the mutability of the defaults, which would be an utter catastrophe.

http://docs.python-guide.org/en/latest/writing/gotchas/

Other languages seem to get it right. Even C++!

    #include <vector>
    static int foo(std::vector<int> x = {}) {
      x.push_back(10);
      return x.size();
    }
    int main (int argc, char const *argv[]) {
      printf("%d\n", foo()); // 1
      printf("%d\n", foo()); // still 1
      return 0;
    }

    def foo(x=[]):
      x.append(10)
      return len(x)
    
    print(foo()) # 1
    print(foo()) # 2? wtf?
C++ its own insane behavior, where default arguments are determined at the call site instead of at runtime. This means your function can get another function's default arguments.

    #include <iostream>
    struct Base {
        virtual void foo(const char *name = "base") {
            std::cout << "Base impl with " << name << " param\n";
        }
    };
    
    struct Derived : public Base {
        virtual void foo(const char *name = "derived") override {
            std::cout << "Derived impl with " << name << " param\n";
        };
    };
    
    int main(void) {
        Base *b = new Derived();
        b->foo();
    }
prints "Derived impl with base param"

Fortunately Python avoided that bit of silliness.

This behavior is pretty reasonable to me.
ES6 got this right too.

This design decision makes me die a little every time I have to do:

  def foo(x=None):
      if not x:
          x = []
      ...
That will get you into trouble as well, please use:

    if x is None:
        x = []
... etc.
Or even "prettier":

  x = [] if x is None else x
I'm partial to:

x = x or []

I'm not familiar enough with the innards of other languages to make any definitive comments on it, but I will say that the challenge is in combining a high-level language with late-binding closures with memoization. I'm not aware of a strategy for that which is always intuitive for every program you write; I think it's just a language design combination that has a very high probability of developing "gotchas" no matter what you do.

Also, on a related note, I really love late binding and memoization, so I'm definitely biased on the side of "I understand why the language designers made this decision, and though the ramifications bother me a little bit, I much prefer this way".

On a third note (because hey, why not), with type hints being a thing now, maybe it would be worth considering a default eval hint. It could default to current behavior, and therefore retain backwards compatibility, but you could add syntax to declare defaults as new variables. That might be a smart compromise.

Small tip: you can shorten your snippet by completely removing main's parameters and the return 0 and still have a valid c++ program.
I did pause to think about this, but sod's law dictates that if you remove the parameters to main, you will have to add them back in again. :)
It may be an easy mistake to make but using None is more consistent and efficient to detect. You can use "if x is None:" for any type; whereas, a function may or may not want to allow empty strings (or even empty lists) and it is easy to use a None check to decide when to apply the desired default.
Can you (or someone) clarify what problem you're talking about? This seems to work as expected for me:

    def foo(x=[]):
        sum = 0
        for a in x: sum += a
        return sum
    print foo([3,5])  # 8
    print foo(x=[3]) # 3
    print foo([]) # 0
    print foo() # 0
Is it the fact that kwargs are required to have defaults? Or what?
The problem is that the default argument is mutable.

  def f(a=[]):
    a.append('v')
    print a

  f(['ok']) # ['ok', 'v']
  f()       # ['v']
  f()       # ['v', 'v']
Ah, thanks.
Nah, do it like this

def foo(i,x=[]):

    x.append(i)
    
    return x
foo(1)

foo(2)

See what happens

Because nobody's made a Python Enhancement Proposal for what should happen instead.