On 24/03/2018 14:44, Steven D'Aprano wrote:
On Sat, Mar 24, 2018 at 07:12:49PM +1000, Nick Coghlan wrote: [...]
At a user experience level, the aim of the scoping limitation is essentially to help improve "code snippet portability".
Consider the following piece of code:
squares = [x**2 for x in iterable]
In Python 2.x, you not only have to check whether or not you're already using "squares" for something, you also need to check whether or not you're using "x", since the iteration variable leaks.
For PEP 572, the most directly comparable example is code like this:
# Any previous binding of "m" is lost completely on the next line m = re.match(...) if m: print(m.groups(0))
In order to re-use that snippet, you need to double-check the surrounding code and make sure that you're not overwriting an "m" variable already used somewhere else in the current scope.
Yes. So what? I'm going to be doing that regardless of whether the interpreter places this use of m in its own scope or not. The scope as seen by the interpreter is not important.
Good for you. But the proposed scoping rules are an extra safeguard for programmers who are less conscientious than you, or for anyone (including you) who is short of time, or misses something. An extra level of protection against introducing a bug is IMO a Good Thing.
If all we cared about was avoiding name collisions, we could solve that by using 128-bit secret keys as variables:
Good luck with that. :-)
We don't need multiple scopes to avoid name collisions, we just need to make sure they're all unique :-)
You could use the same argument to justify "We don't need separate local and global scopes". But we have them, and it makes it easier and safer to cut-and-paste functions. I assume you don't consider that a Bad Thing.
But of course readability counts, and we write code to be read by people, not for the convenience of the interpreter.
For that reason, whenever I paste a code snippet, I'm going to check the name and make a conscious decision whether to keep it or change it, and doing that means I have to check whether "m" is already in use regardless of whether or not the interpreter will keep the two (or more!) "m" variables. So this supposed benefit is really no benefit at all. I still am going to check "m" to see if it clashes.
Same argument, same reply. Good for you - but there's nothing wrong with an extra safety net. And you make essentially the same point a few more times, I won't repeat myself further.
To the extent that this proposal to add sub-function scoping encourages people to do copy-paste coding without even renaming variables to something appropriate for the function they're pasted into, I think this will strongly hurts readability in the long run.
I think it will aid readability, precisely for the reason Nick gives: you need to make fewer checks whether variables are or are not used elsewhere.
With PEP 572, you don't even need to look, since visibility of the "m" in the following snippet is automatically limited to the statement itself:
if (re.match(...) as m): print(m.groups(0)) # Any previous binding of "m" is visible again here, and hence a common
source of bugs is avoided :)
Is this really a "common source of bugs"?
Do you really mean to suggest that we should be able to copy and paste a code snippet into the middle of a function without checking how it integrates with the surrounding code? Because that's what it seems that you are saying. And not only that we should be able to do so, but that it is important enough that we should add a feature to encourage it?
If people make a habit of pasting snippets of code into their functions without giving any thought to how it fits in with the rest of the function, then any resulting bugs are caused by carelessness and slap-dash technique, not the scoping rules of the language.
The last thing I want to read is a function where the same name is used for two or three or a dozen different things, because the author happened to copy code snippets from elsewhere and didn't bother renaming things to be more appropriate. Nevermind whether the interpreter can keep track of which is which, I'm worried about *my* ability to keep track of which is which.
I might be cynical about the professionalism and skills of the average programmer, but even I find it hard to believe that most people would actually do that. But since we're (surely?) going to be taking time to integrate the snippet with the rest of the function, the benefit of not having to check for duplicate variable names evaporates.
We (hopefully!) will be checking for duplicates regardless of whether they are scoped to a single statement or not, because we don't want to read and maintain a function with the same name "x" representing a dozen different things at different times.
I'm not opposed to re-using variable names for two different purposes within a single function. But when I do it, I do it because I made a conscious decision that:
(1) the name is appropriate for both purposes; and
(2) re-using the name does not lead to confusion or make the function hard to read.
I don't re-use names because I've copied some snippet and can't be bothered to change the names. And I don't think we should be adding a feature to enable and justify that sort of poor practice.
Comprehensions have their own scope, and that's at least harmless, if not beneficial, because they are self-contained single expressions. But this would add separate scopes to blocks:
def function(): x = 1 if (spam as x): ... while (ham as x): ... # much later, deep in the function # possibly after some or all of those blocks have ended ... process(x) # which x is this?
This would be three different variables all with the same name "x". To track the current value of x I have to track each of the x variables and which is currently in scope.
I don't think we need sub-function scoping. I think it adds more complexity that outweighs whatever benefit it gives.