Scoped and Typed Variables in Python Blocks

Hello Python fellows

I’m sharing a small language idea :
https://gitlab.com/python_431/scopes/-/blob/main/pep-xxx-scoped-and-typed-bindings.md?ref_type=heads

(well, I’m just pretending it is a pep, but who knows? I spent some time thinking about this)

It introduces the syntax x:: (and x:: some_type) for block-local variables that disappear when the block ends or retrieve previous value.

regards,

JPM (YBM)

That kind of stuff already exists, at least to some degree, in Python.

Why can users, at least as you made it seem like, not just del <name>? And why do we need special syntax for this?

a few less characters, but mostly for this :

i = 1

for i in (1, 2, 3):
nasty stuff with i

i = i*2 # 6 or not. maybe a string “spam spam”

”quick fix” with + del(i) at the exit of the for loop, one line 5 chars : NameError
”quicker fix” with = i:: ; 2 chars no more lines, no error, no more bug, i is 6

updated doc.

del is deleting, not retrieving.

Welcome to this forum. A general remark: It would be good to start with a concise presentation of an ideas here. I do not consider it too helpful to blow everything up to a document entitled “pre-PEP” that somehow mimics the structure of a PEP, without actually meeting the quality criteria for a PEP.

While the idea itself has some merits, your proposal is rather unclear and actually self contradictory.

While from the prosa, it is rather (not completely however) clear what you mean with local scoping of variables, this idea does not fit with your code examples, like when you write:

for i::, j:: int, k: int in iterable:
    BODY

behaves like

try:
    def __pep_block__():
        for __tmp in iterable:
            i, j, k = __tmp
            BODY
    __pep_block__()
finally:
    for __name in ['i', 'j']:
        try:
            del globals()[__name]
        except KeyError:
            pass

This does not at all what you describe:

  • k would be local here (although it does not use the :: marker you have designated as markers for locally scoped variables).
  • i and j are not only locally scoped, but additionally deleted from the globals. That does not make sense either.

So the explanation of the code should be acutally like:

   def __pep_block():
      nonlocal k
      for i,j,k in iterable:
          BODY
   __pep_block()

or possibly like:

    for __name in ['i', 'j']:
        __saved[__name] = globals()[__name]
    try:
        for i,j,k in iterable:
            BODY
    finally:
        for __name in ['i', 'j']:
            globals()[__name] = __saved[__name]
        del __saved

Another alternative would be

   for __private_i,__private_j,k in iterable:
         BODY # In the whole body i and j are replaced
              # by __private_i or __private_j, respectively.
   del __private_i
   del __private_j

In all three cases the private names starting with two underscores would be private names that cannot clash with any existing names. All three options would have slightly different semantics that a true PEP would need to address.

We have a page in the devguide about how propose and make changes to Python. I thought the page was provided somehow when new topics were started in the Ideas category, but maybe it is not?

It is in the pinned post (About the Ideas category) but I don’t know if there’s a way for people to see something like that when they open the new topic box.

1 Like

Sorry for posting a pep style document at first, it was auto-ironic…

The point is to add block local names by using a syntax like var:: or var:: int.

Here is a first draft : rationale.md · main · Python_Is_Here / scopes · GitLab

i = 1
for i:: in range(3):
    print(i)

print(i) # Still 1 or NameError`

which is basically syntactic sugar for:

i = 1

def __implicit_block__(outer_i):
    # outer_i is captured, but *not* overshadowed by i
    for i in range(3):           # <- this “i” is LOCAL to the function
        print(i)

__implicit_block__(i)

print(i) # sill 1 (or NameError)

JP.

What’s the problem you’re trying to solve? Obviously it introduces scopes, but there already are scoping mechanisms (modules, functions, classes) that can be used when someone needs them. In order to use your proposal, someone would need to discover they need it, and they could just learn how to work with existing structures.

To me, this doesn’t add enough value to justify yet another syntax construct to the language, but without a statement of need, I can’t tell whether I agree that there’s a problem, whether the proposal solves it, or whether there might be better ways of solving the problem.

1 Like

Well… to begin with I felt the lack of such a feature in my code sometimes.

In general I do think that scopes of variables being as narrow as needed is good idea : not interfering with other uses of the same variables outside of the block (BUG!), not leaking any irrelevant data after a for/while loop. It is, imha, also coherent with the use of else: (this is something I didn’t consider btw, the “locality” should extent to the else: block too)

Of course one could always create a function to do the same, but creating a function that is only called once makes me feel sad.

Python already has nested scopes! They’re introduced with the two-word keyword “class scope”, and it looks like this:

i = 1
class scope:
    for i in range(3):
        print(i)
print(i) # Still 1

The scope of inner variables is defined by indentation, just like everything else in Python. And if you want something NOT to be scoped down, you can use the global or nonlocal keywords to do it.

5 Likes

Thanks. I think this is a relatively poor argument for new syntax.

Are you sure that “class scope” is a keyword(s) ?

It works with any name, including _, of course. I didn’t think about that elegant “trick”.
Just to nitpick : it uses more lines (class … and every global/nonlocal statements)
But I have to admit that it resolves my main point…

This is why it wasn’t my sole argument…
Anyway the trick with class scope is imho a strong argument against my proposal. Even if I still like it. (This is not an argument, and I’m not here for an argument)
Thanks !

Anyway if class scope (or whatever) does the job my guess is that it will be used only when such a behaviour is needed for the very beginning (because there is already a named defined at an higher level), not when it not known when the loop/with statement is written. My proposal would likely be used, I think, any time you don’t want to deal with subsequent modification introducing new names.
Just like in C : I always write for (int i = 0 ; …) even if there is no i defined before)

I agree, but I want to “yes and…” this.

If you felt you needed something, that’s a great starting point for an improvement idea. But in order to present that feeling to other people, you need to do some work to figure out what you were trying to do that made this need appear. You can’t explain it until you understand it yourself and know why you have this feeling.

I have not felt any need for this feature. Is that because I’m not running into the problem, or because I’m working around it and have gotten used to it?

What about this (protecting global names) :

>>> l = [ list for list in ( [1,2], [3,4] ) ]
l
[[1, 2], [3, 4]]
>>> list(“spam”)
[‘s’, ‘p’, ‘a’, ‘m’]
for list in ( [1,2], [3,4] ): # my proposal use list::
…     print(list)
…
[1, 2]
[3, 4]
>>> list(“spam”)
Traceback (most recent call last):
File “”, line 1, in list(“spam”)
TypeError: ‘list’ object is not callable

Moreover there is a huge difference between class scope and my proposal:
With class scope you have to global/nonlocal all names you don’t want to be local to the block, but you don’t necessarily know them in advance, code changes
With var: you work the other way around : you take the very names you want to be local, you don’t have bother about others

Erratum : my proposal is “var::” or “var:: type” not “var:”/”var: type”, I’ll fix the document asap.