4 ms·
It's not merely that. That for-loops work by assignment rather than creating a new scope, or that if-conditions do not create a new scope for their arms is mos
by Blikkentrekker 6y ago
It's not merely that.
That for-loops work by assignment rather than creating a new scope, or that if-conditions do not create a new scope for their arms is most unusual.
Not only does it not teach programmers to properly reason about scope, but it results into subtle bugs that are easy to miss. Consider the following:
list = []
for x in iterator:
list.append(lambda y: some_code_that_closes_over(x))
This almost certainly does not behave as the programmer intended, for the for-loop does not create a new scope, so all iterations of the loop share the same scope, and thus every closure that closes over `x` handles the same `x`, which will have the value that `x` had at the last loop, thus effectively mutating the closure after appending it to the list, so that the list will only contain effectively identical closures.
The proper way do it is by using the fact that functions do create a scope:
list = []
def loop_function(x):
list.append(lambda y: some_code_that_coses_over(x))
for x in iterator:
loop_function(x)
Certainly code that looks quite hackey to work around the lack of normal, expected block scoping.
- bb88 6y agoHow about not using lambdas which create accidental closures all over and use partial functions, instead? list.append(functools.partial(some_code_that_closes_over, x)) If you replace a lambda with a function definition in the for loop, it's easier to see the scoping issue is actually a closure. And that makes sense because lambda is shorthand for a function.
- Blikkentrekker 6y agoBecause the code that closes over can be arbitrarily complicated. I simply used code that only called a function with a single argument as a minimal example. Apart from that, your code technically does not do the same thing as it calls the inner function with two arguments, whereas mine simply discards the second argument.
- bb88 6y agoBut your argument is about scoping. Replacing the lambda with a function inside the for loop shows the closure plainly. So your complaint can't be about scoping, or maybe this is a bad example of your argument. Maybe your complaint is about the fact that statements inside a lambda isn't called until the lambda is called. But again, lambda's are just functions, and closures are closures. Or maybe your argument is that closures are easy to make in python, which is true. But that doesn't mean the language is bad, since lots of languages use closures.
- Blikkentrekker 6y agoNo, my complaint is, as said, hat for-loops in Python do not create a block scope as they do in about any other language. The closure is simply a way to illustrate that but there are many other problems such as this one: x = something # some lines of code for x in iterator: # something # try to use x again here # x has been re-assigned by the loop In about any other language, the loop would create it's own scope, and thus shadow the existence of the outer `x`, rather than re-assigning it.
- bb88 6y agoSo your first example was clearly a bad example. This was a better example. But even so, this is a lousy example since x is already defined at the top. So the for loop could theoretically use the defined x already. I think what you're looking for is something akin to the following in C: int x = 5; for (int x=0; x<10; x++) { int y = x; } But I would argue that this is terrible C code since the inner x shadows the outer scope leading to confusion as to which x the loop is iterating on. But you could also write the for loop like this: for (x<0; x<10; x++) { ... } So C doesn't dictate the scope of the loop which could be argued by your standards, even more confusing than python. Python just says your scope is just function level. Subtle C code changes can create scoping bugs. So even the scoping example your trying to use is fraught with issues for new developers. Is "function-level" scoping better than "C-style" scoping say? I tend to think that function level scoping forces developers to simplify their code as it discourages symbolic shadows.
- kqr 6y agoNot a technical counter argument, but surely this is a trifle in the grander scheme of things? The hard things about programming, such as modularization at a high level and verification of user input, are orders of magnitude more important than quirks like these? (And if we broaden our view to include more of software engineering in general, like solving the right problems and validating hypotheses with fast feedback, it seems even more insignificant.)
- lmm 6y agoPoor syntax or surface-level semantics make a language exponentially harder to use, because every layer of your program becomes more difficult. You have to make your modules smaller which means you have to split them up more and have more interfaces between them. Etc.
- Blikkentrekker 6y agoBut block scope is all about modularization and ensuring that parts of the program that need not interact do not interact. Python code seems to either be written from a mentality of not at all caring about variable lifetime and making it far longer than it should be, or programmers that do care, and uses classes and functions as makeshift tools to attempt to limit the lifetime of variables to make up for the lack of block scope of the language.
- mkl 6y agoAll you need is "x=x" to make a copy: list = [] for x in iterator: list.append(lambda y, x=x: some_code_that_closes_over(x)) That way the closure is explicit and much clearer when you look at it later. ("list" is a builtin, so not a good variable name.)
- Blikkentrekker 6y ago> All you need is "x=x" to make a copy: Your code is illegal Python on two levels. - Lambdas in Python do not allow assignment at all. - Variables in Python cannot be assigned using themselves in their r.h.s., without first being assigned something else, because Python's scoping is strange. You serve well as an example of a programmer that does not understand Python's semantics here, because they are very counter-intuitive, and you also serve as an example of a programmer that does not understand how scope works at all, even if your example be worked into something of valid Python syntax: thunks = [] for x in "abcd": def thunk(): y = x print(y, end='') thunks.append(thunk) for thunk in thunks: thunk() The output is `dddd`, not `abcd`; the `y = x` part is completely irrelevant in this case, for when the thunk is called, `x` has already been re-assigned, and so `y = x`, is assigned the new value. Again, `x` is re-assigned on every new iteration of the loop, as such the `x` in every single one of those thunks contains the value `x` had at the last iteration of the loop when the loop completes. There are many ways to solve this issue, such as the one I initially gave, but yours isn't one of them and that you thought it was shows the counter-intuitive nature of Python's behavior here. > ("list" is a builtin, so not a good variable name.) Which would be another problem with Python's lack of scope. Shadowing the names of library functions and constants is not problematic in languages with proper block scope.
- mkl 6y agoMaybe you should try running it before jumping straight to insults? My code is perfectly legal in Python and runs exactly as I said. Yours: In [1]: list = [] ...: for x in range(5): ...: list.append(lambda y: y+x) ...: [f(100) for f in list] Out[1]: [104, 104, 104, 104, 104] Mine: In [2]: list = [] ...: for x in range(5): ...: list.append(lambda y, x=x: y+x) ...: [f(100) for f in list] Out[2]: [100, 101, 102, 103, 104] I understand the semantics just fine, and it's very clear what's going on. In my version, each lambda has two parameters, one called y, and one called x. The one called x has a default value, and the default value is whatever the loop variable x's current value is: a copy is made of the loop variable's value, and stored as a default parameter value. There is no assignment, and a variable is not assigned to itself. The parameter has the same name, but the scope is different; within the lambda, the parameter shadows external variables, just as in any function. This is idiomatic Python code, taught to beginners (search for n=n - it's about this same loop thing): https://realpython.com/python-lambda/ https://realpython.com/python-lambda/ You do the same thing when passing parameters through to an inner function, like this: def outer(x, y): def inner(z, x=x): return z+x print(inner(2*y)) Shadowing list in a function scope is fine (just confusing), but your code does it in the global scope. You seem to have a bone to pick with Python for some reason, but to me your attempts at criticism fall completely flat, as they are either factually incorrect or amount to "Python is different from [language x]".