
As it stands now, to create a local scope, you must define a function. However, there are many cases where various things can reasonably be done inline. Preparatory code is often done this way. Good coding practice is generally accepted to be that variables are local if at all possible. However, in direct, inline Python code, we're inherently creating variables with a global scope. We don't actually need a function for this kind of code; but a local scope would often be valuable (as we see with lambda.) Moving things off into a function can create a circumstance where we have to go looking for the function. When something is a "one-shot" as in global preparatory code, that's doing more work, and creating more indirection, than needs actually be done. But global operations have their own pitfalls, one of which is variable collisions. So Python sort of drives us to make "one-use" functions where lambdas are insufficient to the case in order to control locality. You can end up writing things like... def PrepOne(): for MyCount in range(0,10): pass PrepOne() ...which achieves the locality desired for variable MyCount, but is clunky. It also clutters the global namespace with prepOne, which has no good reason to exist. So I'm thinking of something along these lines: local: # <== or whatever syntax makes sense for MyCount in range(0,10): pass So in that example, the code is inline - runs as part of the global, sequential code execution - but the variable MyCount is local and goes "poof" once the local: scope is exited. This is the scoping equivalent of a pair of curly braces in c and c++. I would like to see it be nestable, as scopes are in c/c++: local: # <== or whatever syntax makes sense for MyCount in range(0,10): local: for YourCount in range(0,MyCount+1) pass There, MyCount is available in both scopes (the first local: is an enclosing scope) and YourCount is available only in the second local: scope. Or, local: could be very strict, and require further syntax to incorporate a previous scope, something like this: local: # <== or whatever syntax makes sense for MyCount in range(0,10): local: incorporate MyCount # <== or whatever syntax makes sense for YourCount in range(0,MyCount+1) pass Lastly, I suggest that the global keyword be used to expose global scope variables, just as it is within def functions: TheirCount = 10 local: # <== or whatever syntax makes sense global TheirCount for MyCount in range(0,TheirCount): local: for YourCount in range(0,TheirCount) pass This would also be useful within normal functions, and again allows the program flow to be linear rather than calling a function-within-a-function.

Dear Mous[e], On 29/11/2022 14.49, Anony Mous wrote:
As it stands now, to create a local scope, you must define a function. ...
(inline) inherently?
The larger criticism of the above is the chosen-name (not referring to PEP-008). The opportunity a function gives, is to name a unit of code. This is the way people think - often called "chunking". Whereas 'Apprentices' think (perhaps) one line of code at a time, more experienced pythonista think in larger 'lumps', eg 'need to sum the first ten (non-negative) digits'. The actual code to deliver such a sub-objective is the same, either in-line or as a function. However, deliberating over the choice of name for the function makes the code easy to read - indeed, coming to it later, (and assuming some trust in the author), at first it wouldn't be necessary read the function's code, if the name enabled comprehension. Test-oriented folk will also point-out that the function/sub-objective can be tested, even proven, without regard to other aspects of the code, or the progress of development 'elsewhere'. ...
This is the scoping equivalent of a pair of curly braces in c and c++. I would like to see it be nestable, as scopes are in c/c++:
Aside: statements seeking justification from other languages invite an obvious retort, along the lines of 'use [what you think is] the best tool for the job'.
Please allow appropriate of the word "clunky". This example features two uses of "local:" which contribute nothing except to isolate MyCount and YourCount (sic). In addition to this 'noise' in the code, there is an extra level of indentation for each "local:". (please refer back to 'the early days' and the Python blocks-by-indentation discussions, and the debates over tabs cf how-many-spaces, leading to the idea of code marching off-the-page to the right). Functions (also) enable a tidy form of nesting.
Can Python be described as a "strict" language? Compared to which other languages? ... Perhaps the very flexibility of Python is its power? The attitude of 'we're all adults here' suggests that some ideas are not "good", eg globals; but if one has sufficient reason (or foolishness), then proceed - but at your own risk! (see also Python object-attribute addressing through dotted-notation cf formal getters and setters). One of the challenges I faced coming to Python - and still notice from time-to-time, is accepting the informality, the EAFP (cf LBYL) attitude. However, one lives and learns... OTOH given that programming has been described as "choosing good names", I can't remember the last time a naming-collision/scope-issue proved that I'm not the smartest person in the room... -- Regards, =dn

On Thu, 1 Dec 2022 at 05:13, dn <PythonList@danceswithmice.info> wrote:
Hmm, I'd dispute that when it comes to idea discussions. It's generally helpful to see prior art, and given that a good few of us are fluent in multiple languages (and core devs are likely to be quite familiar with C due to its use in CPython), explaining an idea in terms of another language's features is quite helpful. Of course, it's only helpful to the exact extent that it parallels the other language's feature, which might not work too well without variable declarations, but that's the inherent difficulty of feature-borrowing :) ChrisA

A context manager could be used for this. On exit it should delete the variables created inside it. Someting like this: class Scope: def __enter__(self): self._globals = list(globals().keys()) return self def __exit__(self, exc_type, exc_value, traceback): del_vars = [var for var in globals() if var not in self._globals] for var in del_vars: del globals()[var] Then one can write: with Scope(): a = 1 with Scope(): b = 2 # no more b here # no more a or b here Maybe such an (optimized) context manager could be added to Python? Op 29/11/2022 om 02:49 schreef Anony Mous:

On Thu, 1 Dec 2022 at 06:47, Benedict Verhegghe <bverheg@gmail.com> wrote:
That only works if the Scope class is defined in the same module that you use it, and you only use it at top level. It won't work if imported into another module, nor in a function. Also, it won't allow shadowing of outer names with inner names, a standard feature of most scoping systems (and all sane ones): x = 1 def spam(): x = 2 def ham(): x = 3 print("In ham, x is", x) ham() print("In spam, x is", x) spam() print("At top level, x is", x) Simply removing x from globals will not reveal the previous value. ChrisA

Like most commenters, I think the whole "create an anonymous function then call it" scoping thing is too complex and has too many edge cases to be a good idea. That said, I decided to play around with what I can do to serve the general purpose within existing Python:
d 121
Clearly, this still leaves it up to the programmer to make sure the "local" names aren't already defined and important (i.e. with other values already). I guess you could make this more complex by copying globals(), then restoring those that previously existed, even if they were defined otherwise within the context. But honestly, for the very limited purpose desired, this implementation seems like plenty.

On Mon, 5 Dec 2022 at 06:04, David Mertz, Ph.D. <david.mertz@gmail.com> wrote:
You're not the first to try to use globals() for this, but it means that the context manager works ONLY at top-level. You can't do this with it: def foo(): with local("abc"): a, b = 5, 6 c = a + b d = c ** 2 print(d) print(a) and expect it to work. (Side point, though: if you're deleting a name from globals(), why not just delete it straight out of the dictionary rather than exec'ing?) ChrisA

On Sun, Dec 4, 2022, 2:08 PM Chris Angelico <rosuav@gmail.com> wrote:
You're not the first to try to use globals() for this, but it means that the context manager works ONLY at top-level.
True. I know that. But if you're inside a function, you already have scope containment, so the issue feels like "so what?" But doh! I always forget about which way the locals() / globals() mutability thing goes. A regular del without exec is better, of course.

On Mon, 5 Dec 2022 at 06:24, David Mertz, Ph.D. <david.mertz@gmail.com> wrote:
Sure. Notably, though, it also only works at the exact SAME top-level that it was defined at, unless you mess with introspection. And, of course, it still can't shadow variables, which really makes the whole concept of scoping rather meaningless - like using "var" deep inside a JavaScript function.
But doh! I always forget about which way the locals() / globals() mutability thing goes. A regular del without exec is better, of course.
Fair enough! I think the Py2 exec statement was a bit different, with some weird abilities to mutate the local scope, but in Py3 it's a lot easier to reason about - it just takes whatever dictionary you pass it, and mutates that. So it actually wouldn't make a difference to the mutability question - I think. There might be execptions, I mean exceptions. ChrisA

On Sun, Dec 4, 2022 at 11:08 AM Chris Angelico <rosuav@gmail.com> wrote:
You're not the first to try to use globals() for this, but it means that the context manager works ONLY at top-level.
I agree with most criticism of this proposal, although I'll note that the one place where I'd like something like this is at top level. I often write something like this at top level: __part1 = (some calculation) __part2 = (some other calculation) THING = combine(__part1, __part2) __part1 = __part2 = None If they are large objects and I forget to explictly delete the references, then they won't be garbage collected. Yes, this trivial example could be folded into a single line but that makes it much harder to understand what it's doing. And often those calculations are more complex and can't be written on one line. I can put that in a function which still leaves the function in scope: def __create_thing(): part1 = (some calculation) part2 = (some other calculation) return combine(part1, part2) THING = __create_thing() If we had a top-level-only local statement, I might use it but note that it's still clumsy since I have to make THING non-local: local: part1 = (some calculation) part2 = (some other calculation) nonlocal THING THING = combine(part1, part2) The often-rejected multi-line lambda might be better except those parens at the end are easy to miss: THING = (lambda: part1 = (some calculation) part2 = (some other calculation) return combine(part1, part2))() Looking at all these options, is the cost of adding anything actually worth the benefit? Probably not. --- Bruce

On Mon, 5 Dec 2022 at 08:34, Bruce Leban <bruce@leban.us> wrote:
# put this in your site.py or whatever so it's always available def scope(f): return f() # then do this @scope def THING(): part1 = (some calculation) part2 = (some other calculation) return combine(part1, part2) It's better than multiline lambda, and has all the benefits of a nested scope. It doesn't leave the function lying around - it reuses the name for the value the function returns. ChrisA

On Sun, Dec 04, 2022 at 01:34:13PM -0800, Bruce Leban wrote:
A couple of stylistic points... * I don't know if you have a personal naming convention for double leading underscore names, but to Python and the rest of the community, they have no special meaning except inside a class. So you might want to save your typing and just use a single leading underscore for private names. * You probably don't want to assign the left over private names `__part1` and `__part2` to None. Yes, that frees the references to the objects they are bound to, but it still leaves the names floating around in your globals. Instead, use `del`, which explicitly removes the names from the current namespace, and allows the objects to be garbage collected: _part1 = (some calculation) _part2 = (some other calculation) THING = combine(_part1, _part2) del _part1, _part2 In which case I'm not sure I would even bother with the leading underscores.
If they are large objects and I forget to explictly delete the references, then they won't be garbage collected.
Very true. And when you do forget, what are the consequences? I daresay that your program still runs, and there are no observable consequences.
Looking at all these options, is the cost of adding anything actually worth the benefit? Probably not.
Agreed. Given how rare it is for this sort of thing to actually matter, I think that the correct solution is "remember to del the variable when you are done" not "let's complicate the language". -- Steve

Dear Mous[e], On 29/11/2022 14.49, Anony Mous wrote:
As it stands now, to create a local scope, you must define a function. ...
(inline) inherently?
The larger criticism of the above is the chosen-name (not referring to PEP-008). The opportunity a function gives, is to name a unit of code. This is the way people think - often called "chunking". Whereas 'Apprentices' think (perhaps) one line of code at a time, more experienced pythonista think in larger 'lumps', eg 'need to sum the first ten (non-negative) digits'. The actual code to deliver such a sub-objective is the same, either in-line or as a function. However, deliberating over the choice of name for the function makes the code easy to read - indeed, coming to it later, (and assuming some trust in the author), at first it wouldn't be necessary read the function's code, if the name enabled comprehension. Test-oriented folk will also point-out that the function/sub-objective can be tested, even proven, without regard to other aspects of the code, or the progress of development 'elsewhere'. ...
This is the scoping equivalent of a pair of curly braces in c and c++. I would like to see it be nestable, as scopes are in c/c++:
Aside: statements seeking justification from other languages invite an obvious retort, along the lines of 'use [what you think is] the best tool for the job'.
Please allow appropriate of the word "clunky". This example features two uses of "local:" which contribute nothing except to isolate MyCount and YourCount (sic). In addition to this 'noise' in the code, there is an extra level of indentation for each "local:". (please refer back to 'the early days' and the Python blocks-by-indentation discussions, and the debates over tabs cf how-many-spaces, leading to the idea of code marching off-the-page to the right). Functions (also) enable a tidy form of nesting.
Can Python be described as a "strict" language? Compared to which other languages? ... Perhaps the very flexibility of Python is its power? The attitude of 'we're all adults here' suggests that some ideas are not "good", eg globals; but if one has sufficient reason (or foolishness), then proceed - but at your own risk! (see also Python object-attribute addressing through dotted-notation cf formal getters and setters). One of the challenges I faced coming to Python - and still notice from time-to-time, is accepting the informality, the EAFP (cf LBYL) attitude. However, one lives and learns... OTOH given that programming has been described as "choosing good names", I can't remember the last time a naming-collision/scope-issue proved that I'm not the smartest person in the room... -- Regards, =dn

On Thu, 1 Dec 2022 at 05:13, dn <PythonList@danceswithmice.info> wrote:
Hmm, I'd dispute that when it comes to idea discussions. It's generally helpful to see prior art, and given that a good few of us are fluent in multiple languages (and core devs are likely to be quite familiar with C due to its use in CPython), explaining an idea in terms of another language's features is quite helpful. Of course, it's only helpful to the exact extent that it parallels the other language's feature, which might not work too well without variable declarations, but that's the inherent difficulty of feature-borrowing :) ChrisA

A context manager could be used for this. On exit it should delete the variables created inside it. Someting like this: class Scope: def __enter__(self): self._globals = list(globals().keys()) return self def __exit__(self, exc_type, exc_value, traceback): del_vars = [var for var in globals() if var not in self._globals] for var in del_vars: del globals()[var] Then one can write: with Scope(): a = 1 with Scope(): b = 2 # no more b here # no more a or b here Maybe such an (optimized) context manager could be added to Python? Op 29/11/2022 om 02:49 schreef Anony Mous:

On Thu, 1 Dec 2022 at 06:47, Benedict Verhegghe <bverheg@gmail.com> wrote:
That only works if the Scope class is defined in the same module that you use it, and you only use it at top level. It won't work if imported into another module, nor in a function. Also, it won't allow shadowing of outer names with inner names, a standard feature of most scoping systems (and all sane ones): x = 1 def spam(): x = 2 def ham(): x = 3 print("In ham, x is", x) ham() print("In spam, x is", x) spam() print("At top level, x is", x) Simply removing x from globals will not reveal the previous value. ChrisA

Like most commenters, I think the whole "create an anonymous function then call it" scoping thing is too complex and has too many edge cases to be a good idea. That said, I decided to play around with what I can do to serve the general purpose within existing Python:
d 121
Clearly, this still leaves it up to the programmer to make sure the "local" names aren't already defined and important (i.e. with other values already). I guess you could make this more complex by copying globals(), then restoring those that previously existed, even if they were defined otherwise within the context. But honestly, for the very limited purpose desired, this implementation seems like plenty.

On Mon, 5 Dec 2022 at 06:04, David Mertz, Ph.D. <david.mertz@gmail.com> wrote:
You're not the first to try to use globals() for this, but it means that the context manager works ONLY at top-level. You can't do this with it: def foo(): with local("abc"): a, b = 5, 6 c = a + b d = c ** 2 print(d) print(a) and expect it to work. (Side point, though: if you're deleting a name from globals(), why not just delete it straight out of the dictionary rather than exec'ing?) ChrisA

On Sun, Dec 4, 2022, 2:08 PM Chris Angelico <rosuav@gmail.com> wrote:
You're not the first to try to use globals() for this, but it means that the context manager works ONLY at top-level.
True. I know that. But if you're inside a function, you already have scope containment, so the issue feels like "so what?" But doh! I always forget about which way the locals() / globals() mutability thing goes. A regular del without exec is better, of course.

On Mon, 5 Dec 2022 at 06:24, David Mertz, Ph.D. <david.mertz@gmail.com> wrote:
Sure. Notably, though, it also only works at the exact SAME top-level that it was defined at, unless you mess with introspection. And, of course, it still can't shadow variables, which really makes the whole concept of scoping rather meaningless - like using "var" deep inside a JavaScript function.
But doh! I always forget about which way the locals() / globals() mutability thing goes. A regular del without exec is better, of course.
Fair enough! I think the Py2 exec statement was a bit different, with some weird abilities to mutate the local scope, but in Py3 it's a lot easier to reason about - it just takes whatever dictionary you pass it, and mutates that. So it actually wouldn't make a difference to the mutability question - I think. There might be execptions, I mean exceptions. ChrisA

On Sun, Dec 4, 2022 at 11:08 AM Chris Angelico <rosuav@gmail.com> wrote:
You're not the first to try to use globals() for this, but it means that the context manager works ONLY at top-level.
I agree with most criticism of this proposal, although I'll note that the one place where I'd like something like this is at top level. I often write something like this at top level: __part1 = (some calculation) __part2 = (some other calculation) THING = combine(__part1, __part2) __part1 = __part2 = None If they are large objects and I forget to explictly delete the references, then they won't be garbage collected. Yes, this trivial example could be folded into a single line but that makes it much harder to understand what it's doing. And often those calculations are more complex and can't be written on one line. I can put that in a function which still leaves the function in scope: def __create_thing(): part1 = (some calculation) part2 = (some other calculation) return combine(part1, part2) THING = __create_thing() If we had a top-level-only local statement, I might use it but note that it's still clumsy since I have to make THING non-local: local: part1 = (some calculation) part2 = (some other calculation) nonlocal THING THING = combine(part1, part2) The often-rejected multi-line lambda might be better except those parens at the end are easy to miss: THING = (lambda: part1 = (some calculation) part2 = (some other calculation) return combine(part1, part2))() Looking at all these options, is the cost of adding anything actually worth the benefit? Probably not. --- Bruce

On Mon, 5 Dec 2022 at 08:34, Bruce Leban <bruce@leban.us> wrote:
# put this in your site.py or whatever so it's always available def scope(f): return f() # then do this @scope def THING(): part1 = (some calculation) part2 = (some other calculation) return combine(part1, part2) It's better than multiline lambda, and has all the benefits of a nested scope. It doesn't leave the function lying around - it reuses the name for the value the function returns. ChrisA

On Sun, Dec 04, 2022 at 01:34:13PM -0800, Bruce Leban wrote:
A couple of stylistic points... * I don't know if you have a personal naming convention for double leading underscore names, but to Python and the rest of the community, they have no special meaning except inside a class. So you might want to save your typing and just use a single leading underscore for private names. * You probably don't want to assign the left over private names `__part1` and `__part2` to None. Yes, that frees the references to the objects they are bound to, but it still leaves the names floating around in your globals. Instead, use `del`, which explicitly removes the names from the current namespace, and allows the objects to be garbage collected: _part1 = (some calculation) _part2 = (some other calculation) THING = combine(_part1, _part2) del _part1, _part2 In which case I'm not sure I would even bother with the leading underscores.
If they are large objects and I forget to explictly delete the references, then they won't be garbage collected.
Very true. And when you do forget, what are the consequences? I daresay that your program still runs, and there are no observable consequences.
Looking at all these options, is the cost of adding anything actually worth the benefit? Probably not.
Agreed. Given how rare it is for this sort of thing to actually matter, I think that the correct solution is "remember to del the variable when you are done" not "let's complicate the language". -- Steve
participants (10)
-
Anony Mous
-
Barry
-
Benedict Verhegghe
-
Bruce Leban
-
Chris Angelico
-
David Mertz, Ph.D.
-
dn
-
Jeremiah Gabriel Pascual
-
Paul Bryan
-
Steven D'Aprano