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??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??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. #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?This design decision makes me die a little every time I have to do:
def foo(x=None):
if not x:
x = []
... if x is None:
x = []
... etc. x = [] if x is None else xx = x or []
#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.
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.
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?def foo(i,x=[]):
x.append(i)
return x
foo(1)foo(2)
See what happens
def f(a=[]):
a.append('v')
print a
f(['ok']) # ['ok', 'v']
f() # ['v']
f() # ['v', 'v']