5 ms·
>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 ev
by 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>]
- quietbritishjim 3y agoThis is your first snippet (expanded to two lines and max index reduced from 5 to 3): lambda_list = [lambda: i for i in range(3)] results = [x() for x in lambda_list] It expands to this: i = 0 f0 = lambda: i i = 1 f1 = lambda: i i = 2 f2 = lambda: i results = [f0(), f1(), f2()] The body of those functions f0, f1, f2, each looks in the function's own dictionary of local variables for "i". But it doesn't exist there, so it looks in the enclosing scope (and keeps doing this recursively up to global scope if necessary). At the next scope it does find a variable "i". This happens at the point you call the function (the same as anything else inside the definition of a function), in this case on that last line when you assign to results. By that point, the "i" variable in the enclosing scope has value 2. So results = [2, 2, 2]. [edit: this feature of functions in Python (variables are looked up by name when the function is run) is what lets you define functions foo() and bar() in a module / script that call each other. When the first one is defined, the other one doesn't exist yet, but you're not actually running the code inside the function so it doesn't matter. By the time you come to call it later, the other function does exist in the enclosing (module level) scope so the lookup succeeds.] This is your second snippet (I changed one of the variable names from i to j for clarity, but you can rename it back and it would work the same way): lambda_list = [lambda j=i: j for i in range(3)] results = [x() for x in lambda_list] it expands like this: i = 0 f0 = lambda j=i: j i = 1 f1 = lambda j=i: j i = 2 f2 = lambda j=i: j results = [f0(), f1(), f2()] But defaults of function arguments are evaluated at the point the function is declared, not when it's called (as noted elsewhere in this thread - hence the def foo(x=[]) gotcha!). You can imagine the second line of code above is like this, just to really drive the point home [edit: I originally used a made up syntax, but I updated to code that actually works]: f0 = lambda j: j f0.__defaults__ = i, f0.__kwdefaults__ = {"j": i} So, f0 has the specific value 0 baked into it, and similarly f1 and f2 have 1 and 2. Therefore results = [0, 1, 2].
- acqq 3y agoI've tried to see __defaults__ and __kwdefaults__ here: i = 7 lambda_list = [lambda: i for i in range(3)] results = [x() for x in lambda_list] print( i ) for k in range( 3 ): print( lambda_list[k] ) print( lambda_list[k].__defaults__ ) print( lambda_list[k].__kwdefaults__ ) lambda_list = [lambda j=i: j for i in range(3)] results = [x() for x in lambda_list] print( i ) for k in range( 3 ): print( lambda_list[k] ) print( lambda_list[k].__defaults__ ) print( lambda_list[k].__kwdefaults__ ) print( i ) The output is: 7 <function <listcomp>.<lambda> at 0xB2> None None <function <listcomp>.<lambda> at 0xBC> None None <function <listcomp>.<lambda> at 0xC6> None None 7 <function <listcomp>.<lambda> at 0xD0> (0,) None <function <listcomp>.<lambda> at 0xDA> (1,) None <function <listcomp>.<lambda> at 0xE4> (2,) None 7 If I understand it, __kwdefaults__ are always None and the __defaults__ used only in the second case. I've also tried to see the "i" as not being inside of the closure, but it remains 7, not like (as noted by 'remram here): i = 9 y = 1 for y in range( 1,-1,-1 ): try: x = 4 / y except Exception as i: pass print( i ) Which gives, for me, fascinating: 9 Traceback (most recent call last): File "p.py", line 10, in <module> print( i ) ^ NameError: name 'i' is not defined. Did you mean: 'id'? I'm learning a lot here.
- pmarreck 3y ago> Incredibly useful feature For something so "incredibly useful," I've literally never felt the need for something like this in any other language in 20+ years of coding Elixir's deep pattern-matching, on the other hand, I miss in LITERALLY EVERY OTHER language I have to work in