6 ms·
Meanwhile Python has received this same feature request many times over the years, and the answer is always that it would break existing code for little major b
by nerdponx 3y ago
Meanwhile Python has received this same feature request many times over the years, and the answer is always that it would break existing code for little major benefit https://discuss.python.org/t/make-lambdas-proper-closures/10553 https://discuss.python.org/t/make-lambdas-proper-closures/10...
Given how much of an uproar there was over changing the string type in the Python 2 -> 3 transition, I can't imagine this change would ever end up in Python before a 4.0.
Cue someone arguing about how bad Python is because it won't fix these things, and then arguing about how bad Python is because their scripts from 2003 stopped working...
- sneak 3y agoSomehow, Go managed to not break old code and also fix the problem. I think this is a good case of Python not fixing things, given that a fix exists that solves both problems.
- pcl 3y ago> To ensure backwards compatibility with existing code, the new semantics will only apply in packages contained in modules that declare go 1.22 or later in their go.mod files.
- nerdponx 3y agoRight, and Go has the luxury of being a compiler that generates reasonably portable binaries, while Python requires the presence of an interpreter on the system at run time.
- josephg 3y agoThe same trick would work with python just as well. There’s nothing about Python’s status as an interpreter which would stop them from adding a python semantic version somewhere in each python program - either in a comment at the top of each source file or in an adjacent config file. The comment could specify the version of python’s semantics to use, which would allow people to opt in to new syntax (or opt out of changes which may break the code in the future). Eg # py 3.4
- Cthulhu_ 3y agoYeah, it would just mean that the interpreter - just like the Go compiler - would need to have the equivalent of "if version > 3.4 do this, else do that". Which is fine for a while, but I can imagine it adds a lot of complexity and edge cases to the interpreter / compiler. Which makes me think that a Go 2.0 or Python 4 will mainly be about removing branches and edge cases from the compiler more than making backwards-incompatible language changes.
- josephg 3y agoThis is the direction multiple languages are moving in. Go and Rust both have something like this. (In Rust they're called "editions"). I think its inevitable that compilers get larger over time. I hope most of the time they aren't too onerous to maintain - there aren't that many spec breaking changes between versions. But if need be, I could also imagine the compiler eventually deprecating old versions if it becomes too much of an issue. Arguably C & C++ compilers do the same thing via -std=c99 and similar flags at compile time. Anyway, nothing about this is special or different with python. I bet the transition to python 3 would have been much smoother if scripts could have opted in (or opted out) of the new syntax without breaking compatibility.
- lloeki 3y ago> Python requires the presence of an interpreter on the system at run time. A runtime interpreter does not prevent Perl to do similar things via `use 5.13` Python has `from future` with similar abilities, it would absolutely be possible to do the same as Perl and Go and fix what needs to be fixed without breaking old code. One could design a `import 3.22` and `from 3.22 import unbroken_for` and achieve the same thing.
- IshKebab 3y agoPython could very easily have a similar mechanism. Hell even CMake manages to do this right, and they got "if" wrong. The Python devs sometimes seem stubbornly attached to bugs. Another one: to reliably get Python 3 on Linux and Mac you have to run `python3`. But on Windows there's no `python3.exe`. Will they add one? Hell no. It might confuse people or something. Except... if you install Python from the Microsoft Store it does have `python3.exe`.
- wrboyce 3y agoI’ve not run “python3” in years on my Mac, and I’m almost certain I never type it into Linux machines either; either I’m losing my mind, or there are some ludicrous takes in this thread.
- rat9988 3y agoYou are surely losing your mind then. Python3 isn't something esoteric.
- wrboyce 3y agoEntirely possible, but my point was I just type “python” and Python 3 happens. Do modern OS even come with Python 2 anymore? I’m not claiming any mystery about Python, just disputing how the modern version is invoked.
- erik_seaberg 3y ago13.5.2 has /usr/bin/python3 (it’s 3.9.6) but not python2 or just python. Not sure when they changed, and YMMV with Homebrew.
- lmm 3y ago> I just type “python” and Python 3 happens. That was the old way. Python now recommends against installing Python3 in a way that does that, and most modern *nix don't.
- 3y ago
- wrboyce 3y agoBy letting you specify a language version requirement? Not exactly backwards compatible (because it is explicitly not, as per the article). Python doesn’t make breaking changes in non-major versions, so as mentioned by the upthread comment the appropriate place for this change would be in Python 4. Given the above, I’m really not sure what point you think you’re making in that final paragraph.
- carbotaniuman 3y agoThis seems weird to given the number of breakages and standard library changes I seem to run into every version.
- wrboyce 3y agoReally? I find that surprising. I don’t write as much code as I used to but I’ve been writing Python for a long time and the only standard library breakages that come to mind were during the infamous 2 -> 3 days. What sort of problems are have you faced upgrading minor versions?
- orbisvicis 3y agoThe docs are full of remarks like "removed in 3.0 and reintroduced in 3.4" or "deprecated in 3.10", etc. A big one is the removal of the loop parameter in asyncio, but a lot of asyncio internals are (still?) undergoing significant changes, as getting the shutdown behavior correct is surprisingly difficult. Personally it's never cause me any issues - I'm always on board with the changes.
- nerdponx 3y agoAsyncio was explicitly marked as provisional for years and most of the incompatible changes happened during that time. Same goes for typing. The rest of the language is very very stable.
- nayuki 3y agoOne of the few that I remember is fractions.gcd() -> math.gcd(). See https://docs.python.org/3.0/library/fractions.html#fractions.gcd https://docs.python.org/3.0/library/fractions.html#fractions... , https://docs.python.org/3.5/library/fractions.html#fractions.gcd https://docs.python.org/3.5/library/fractions.html#fractions... , https://docs.python.org/3.9/library/fractions.html https://docs.python.org/3.9/library/fractions.html (gone), https://docs.python.org/3.5/library/math.html#math.gcd https://docs.python.org/3.5/library/math.html#math.gcd
- kazinator 3y agoIf the change /doesn't/ break old code, it's also poorly justified. It means code doesn't care about the issue being addressed. The feature is only justified if it changes existing code, such that bugs you didn't even know about are fixed. I.e. people read about the issue, investigate their code bases and go, oh hey, we actually have a latent bug here which the change will fix.
- esrauch 3y agoNew code written today will use the new version and have the correct behavior from day 1. Old code that is maintained will eventually be upgraded, which yes does come with work sometimes where you realize your code works on version X but not version X+10 and you do a combination of tests and reading patch notes to see what changed.
- kazinator 3y agoThere is no "correct" behavior here; either one is a valid choice that can be documented and that programs can rely on and exploit. Code doesn't care about when it's written, only what you run it on, and with what compatibility options. E.g. one possibility is that ten-year-old code that wrongly assumed the opposite behavior, and has a bug, will start to work correctly on the altered implementation.
- jjnoakes 3y agoThere's a second motivation in my opinion. Code might work today without the change, but it could be because the author originally wrote buggy code, caught it in testing, and had to waste time tracking it down and understanding nuances that don't need to be there. Once they figured that out, they implemented an ugly workaround (adding an extra function parameter to a goroutine or shadowing the loop variable with n := n). Good language designers want to avoid both wasting developer's time and requiring ugly workarounds. Making a change that does both, especially if it doesn't break old code, is great imo.
- kazinator 3y ago
- baq 3y agoPython probably could change this with a from __future__ import, i.e. in the same way.
- travisd 3y agoIt's worth noting that it's much less of a problem in Python due to the lack of ergonomic closures/lambdas. You have to construct rather esoteric looking code for it to be a problem. add_n = [] for n in range(10): add_n.append(lambda x: x + n) add_n[9](10) # 19 add_n[0](10) # 19 This isn't to say it's *not* a footgun (and it has bit me in Python before), but it's much worse in Go due to the idiomatic use of goroutines in a loop: for i := 0; i < 10; i++ { go func() { fmt.Printf("num: %d\n", i) }() }
- hinkley 3y agoEveryone else solved this problem by using list comprehensions instead. Rob has surely heard of those.
- lmm 3y agoOf the two comprehension syntaxes in Haskell, Python picked the wrong one. Do notation (or, equivalently, Scala-style for/yield) feels much more consistent and easy to use - in particular the clauses are in the same order as a regular for loop, rather than the middle-endian order used by list comprehensions.
- eru 3y agoHaskell has both do-notation and list comprehension. Comprehension in both Python and Haskell (for both lists and other structures) use the same order in both language, as far as I remember.
- lmm 3y ago> Haskell has both do-notation and list comprehension. Right, and do-notation is the one everyone uses, because it's better. Python picked the wrong one. > Comprehension in both Python and Haskell (for both lists and other structures) use the same order in both language, as far as I remember. It may be the same order as Haskell but it's a terrible confusing order. In particular if you want to go from a nested list comprehension to a flat one (or vice versa) then you have to completely rearrange the order it's written in, whereas if you go from nested do-blocks to flat do-blocks then it all makes sense.
- orbisvicis 3y agoIs it a bug? I've always depended on late-binding closures and I think even recently in a for loop, not that I'm going to go digging. You can do neat things with multiple functions sharing the same closure. If you don't want the behavior bind the variable to a new name in a new scope. From the post I get the sense that this is more problematic for languages with pointers.
- lmm 3y agoIMO it's a misdesign in the same way as e.g. JavaScript's "this". Most languages figured out 40 or so years ago that scoping should be lexical.
- orbisvicis 3y agoThe scope is lexical, the lookup is dynamic. What you want is for each loop iteration to create a new scope, which I would categorize as "not lexical".
- lmm 3y agoBy that argument a recursive function shouldn't create a new scope every time it recurses, and a language that fails Knuth's 1964 benchmark of reasonable scoping (the "man or boy test") would be fine. The loop body is lexically a block and like any other block it should have its own scope every time it runs.
- Dylan16807 3y agoAlso, loop bodies already did have their own scope each iteration. I wouldn't say either behavior is non-lexical. The only thing changing is which lexical scope these variables go into.
- lmm 3y agoIf the loop "variable" (and IMO thinking of it as a variable is halfway to making the mistake) is in a single scope whose lifetime is all passes through the loop body, that's literally non-lexical; there is no block in the program text that corresponds to that scope. Lexically there's the containing function and the loop body, there's no intermediate scope nestled between them.
- masklinn 3y agoPython is in a larger bind because it only has function scoping and variable declaration is implicit. It does not have sub-function scopes. So does not really have a good way to fix the issue, even by using a different keyword as JS did. OTOH default parameters being evaluated at function definition make mitigating it relatively simple.
- codeflo 3y agoYeah, block scoping is one of those "weird CS ideas" that I'm sure at some point early in Python's design was deemed too complicated for the intended audience, but is also quite a natural way to prevent some human errors. JavaScript made the same mistake and later fixed it (let/const).
- kzrdude 3y agoI love python, but it's one of the biggest annoyances. Local variables like in Lua make a lot of sense.
- Cthulhu_ 3y agoI'm not a computer scientist so I can't rule whether function scope was a mistake, and can't see how block scoping would be considered too complicated, I personally think it fits much better with my mental model. Then again, Python doesn't have blocks in the traditional sense of the word IIRC, in C style languages the accolades are a pretty clear delineator. Parts of my previous job were terrible because it had JS functions thousands of lines of code long where variables were constantly reused (and often had to be unset before another block of code). That said, that wasn't the fault of function scope per se, but of a bad but very productive developer.
- masklinn 3y agoTBF you can have block scoping in an indentation-based language, though it probably help to merge the too, as in Haskell: `let…in` will define variables in the `let` clause, and those variables are only accessible in the `in` clause (similarly case…of)