6 ms·
First one is annoying, but it makes sense, to some degree. You say the default is this particular instance that gets created when you define the function. Seco
by gcbirzan 3y ago
First one is annoying, but it makes sense, to some degree. You say the default is this particular instance that gets created when you define the function.
Second is just how things work, the function is a closure that evaluates the non local variable when you run it.
Third one... Iterating over the generator does NOT yield an empty list, it raises StopIterator. As does trying to iterate after a generator is done, so there's no way for list to know what's happening. You can argue that's a bad design, but I'm not so sure, an iterator is something you need to make sure you don't try to iterate over twice...
I don't get the 4th. Comparison operators have the same precedence, so yeah.
- gpm 3y ago> Second is just how things work, the function is a closure that evaluates the non local variable when you run it. In python, yes. In other languages, not necessarily. There are two ways that this could reasonably not be the result: 1. For i in iterator could create a new variable i (shadowing the old i) on each iteration of the loop. This isn't how python works (which also means you can do things like access i after a for loop terminates) but there are languages that work that way. 2. Capturing an integer could copy that integer, instead of copying a pointer to a value that can change in the future. Again, not how python works, but how some languages work. The first in particular I think is more intuitive in other languages, and leads to other footguns in python as well, e.g.: for i in range(5): for i in range(10): pass print("Finished loop iteration", i) # I meant 0, 1, 2, 3, 4. Instead I got 9 9 9 9 9
- BoppreH 3y ago> First one is annoying, but it makes sense, to some degree. You say the default is this particular instance that gets created when you define the function. Could just as easily be an expression that's reevaluated on each call. In fact it's what most other languages do, making it twice as bad a decision. Even Javascript gets it right: function fn(arg=[]) { return arg; } fn() === fn() // false > Second is just how things work, the function is a closure that evaluates the non local variable when you run it. That's again a decision made by Python. Some other languages behave as if each iteration had declared a different variable. Here's Javascript again, you can even use `const` for the iterating variable: const fns = []; for (const i of [1, 2, 3]) { fns.push(() => console.log(i)); }; fns.forEach(fn => fn()); // 1 // 2 // 3 > Third one... Iterating over the generator does NOT yield an empty list, it raises StopIterator. Sorry, my phrasing was imprecise. It's not doing 'yield []', but an exhausted generator does behave like an empty generator. Maybe they could have made it so the first exhaustion raises StopIterator, and subsequent ones a different exception? Like reading from a closed channel. > I don't get the 4th. Comparison operators have the same precedence, so yeah. It's not precedence, it's Comparison Chaining, the same feature that enables '1 < a <= 10'. If the operators had simple precedence, it would be equivalent to '(1 < a) <= 10' or '1 < (a <= 10)', but Comparison Chaining evaluates it as '(1 < a) and (a <= 10)'. Useful for specifying ranges, but a foot gun in other scenarios, like in my example.
- memefrog 3y ago>That's again a decision made by Python. Some other languages behave as if each iteration had declared a different variable. Here's Javascript again, you can even use `const` for the iterating variable: But then can you use i after the loop? In Python you can use i after the loop, which can be useful (e.g. if you break out) for i in range(10): if condition(i): break else: print("didn't break") print("broke at", i) It is also easy to "fix": ll = [lambda i=i: print(i) for i in range(5)] This issue is always going to be an issue for Python because it doesn't have explicit variable declaration. In C++ you have the difference between for (int i = 0;; i++) // and int i; for (i = 0;; i++) while in Python there's no way to write 'declare i here so it will be scoped outside the loop', which if it existed would make the behaviour you desire a reasonable alternative. Python variables are always scoped to the surrounding function. for i in range(5): if i % 2 == 0: q = i print(q) #=> 0 0 2 2 4 >Could just as easily be an expression that's reevaluated on each call. Not ideal but it is consistent with other parts of the language. This is just one of those things you need to learn and in practice isn't really an issue. The fact it's different from other languages is not a relevant concern at all. 'Being like JS' is not a virtue. >Sorry, my phrasing was imprecise. It's not doing 'yield []', but an exhausted generator does behave like an empty generator. Maybe they could have made it so the first exhaustion raises StopIterator, and subsequent ones a different exception? Like reading from a closed channel. Iteration is just calling __next__. It's useful to iterate over things more than once sometimes. IMO, this: y = iter(x) for z in y: do(z) if condition(z): break for z in y: do2(z) is bit nicer than this: flag = False for z in y: if flag: do(z) if condition(z): flag = True else: do2(z) >Comparison Chaining Incredibly useful feature. I've never seen anyone write 'a < b == c' so this seems a bit artificial. I'd hardly describe it as a footgun.
- acqq 3y agoSo can somebody explain to me, step by step with the "elements behind the curtain", what is actually going on in these two cases: >>> [ x() for x in [lambda: i for i in range(5)] ] [4, 4, 4, 4, 4] Why? >>> [ x() for x in [lambda i=i: i for i in range(5)] ] [0, 1, 2, 3, 4] And why? Starting from the fact that the following give: >>> [lambda: i for i in range(5)] [<function <listcomp>.<lambda> at 0xC22>, <function <listcomp>.<lambda> at 0xCAE>, <function <listcomp>.<lambda> at 0xC9A>, <function <listcomp>.<lambda> at 0xCD6>, <function <listcomp>.<lambda> at 0xCC2>] >>> [lambda i=i: i for i in range(5)] [<function <listcomp>.<lambda> at 0xCF4>, <function <listcomp>.<lambda> at 0xCFE>, <function <listcomp>.<lambda> at 0xD08>, <function <listcomp>.<lambda> at 0xD12>, <function <listcomp>.<lambda> at 0xCCC>]
- accoil 3y agoI can see how it happened, but the first one does not make sense. Most people would expect argument defaulting to be per execution. If the argument is None, instantiate it with this new value. I'm curious what making it shared and mutable achieves.