Scoped and Typed Variables in Python Blocks

Of course not, that part was a joke :slight_smile: It’s like the “robot face” list clone operator (stuff[:]), or how Python has full support for brace-delimited blocks - you write them as #{ and #}` and of course make sure that you indent correctly (just like people do in C anyway). Point is, Python has full support for this, without needing it to be a keyword per se.

1 Like

I can’t speak for the OP, but I’m pretty sure that the reason I don’t feel the need for this is because I hate over-long functions. My coding style is very much based around small, self-contained functions. Unlike the OP, I don’t mind functions that only get called once. What I do mind, is functions that are too big to have a simple behaviour that can be expressed in a short name, or functions that include sections of code inline that can be named and given their own identity.

So because I naturally tend towards small functions, I find the need for a scope that’s smaller than function scope to be almost non-existent.

IMHO, apart from solid syntax already existing for this, should we really focus on this? Some keyword for namespacing a variable would be better, both for readability, and possibly future extensions.

Realistic speaking though, one could simply rename variables so they fit. Sure it’s not always perfect, and it somewhat hurts when the perfect name is already used, but what can you do?

Moreover, even if this would be considered, would :: really be the best syntax for the task? It reminds me of some slicing, like ...[::-1], and if we now have ...[x::y] is that some newly created, invalidly placed, x, with an annotation of y?

Sadly, I don’t really think this will work out, as cool as the idea might be.

Sorry, I should have put my question in <rhetorical> tags! :sweat_smile:
I meant it as a form of pretty much the same advice I give to anyone new in Ideas when it seems like it’s what they need to hear/learn.[1]

A good exercise for the OP, in sharing ideas, is to try to project and guess at what the thought process will be of a reader who is basically neutral towards the idea but doesn’t see why it would be useful.

If I’m not open to being convinced, there’s no point in trying to imagine my perspective. So assume that I can be convinced of anything, given enough evidence (and cookies!), and then try to figure out how you’re going to convince me. (hint: delicious cookies!)

Not only do I tend towards short functions, but I see nothing wrong with a long function with comments and long variable names, if that’s what the use-case demands.


  1. Aside: I’m forever growing/learning, in terms of how to help out. My DMs are always open. :slight_smile: ↩︎

sophistry never killed anyone :slight_smile:

Another syntax could be !var : for !var in ... / for !var: int in ...

The point here is not to make syntax that makes no sense (at least to me).

! is the Boolean nit operator in most languages, and to be honest, it is wired that it is not in Python (~ is used for __invert__), although it appears in the not-equals operator !=.

But that’s besides the point, that syntax is - at least in my opinion - less readable than the other one, as there is no connection with what it does. Most people from a CS background, will likely think of !x as being the Boolean inverse of x, although ~x and ē (letter with a bar on top, I could only find the character for e) are somewhat common too.

But yeah, tldr, that newly proposed syntax looks even worse imo. Maybe try and think about a syntax (or keyword, it doesn’t need to be new syntax all the time), that is easily associated with the feature.

1 Like

I agree !var is confusing, I thought of different syntax : i:: i! i^ even for local i, local j, k in ... so far var:: makes more sense imho

Well yeah, i:: looks like slicing. a! looks like a factorial of a. a^ is not like a to the power of n, but rather like a^ba xor b (Boolean, invokes the __xor__ method.

The local keyword does somewhat work, but it sadly makes such lines as for a,b,c,d in my_it_with_unpacking: ... long. Perhaps, we could specify a local, which is not valid in such a situation, but defined previously.

local x, y, z
for x, y, z in vectors:
    draw_vector(x, y, z)

Where afterwards, the x, y, z have the same values they had before. The only problem I see here, is that it is somewhat wired, that before x, y, z are defined, we say that they are local (although that is similar to the global keyword).

Maybe, to improve readability, one could also define a restore keyword, or some builtin restore function, where the values x,y,z had before the block executed, are restored. That would be hard to steer though (e.g. nested blocks), and having a function that determines the way the AST is built is bad too.

Maybe though users should just name their variables on ways, where such names do not overlap.

1 Like

maybe : for let x, let y, z in ... or for my x, my y, z in ... ?

I don’t need this bike shed, but if we really want to build it, I’d paint it thus:

with local x, y, z:
  for x, y, z in vectors:
    draw_vector(x, y, z)

The outdent would then signify the restore keyword/function.

However, this doesn’t offer any advantages over @Rosuav’s class scope “keyword”. And the repetition would make it unattractive for longer variable names than x, y, z.

So overall -1 for me

class scope is nice, BUT

  • it’s opt-out : you have to use global to tag ALL non local names
  • my, local, let, :: are opt-in : you only have to tag local names

Concerns About Block-Scoped and Typed Variables

Thank you to everyone who took the time to comment on the proposal.
Below is a clarification responding to the points raised.

Syntax

i = 1

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

print(i) # Still 1 or NameError

1. “What problem does this solve? We already have functions/classes.”

Block scope is technically achievable today, but only by adding extra functions or classes.
This can impact readability, debugging clarity, and structural simplicity.
The proposal provides a lightweight, explicit way to express:
“this variable belongs only to this block.”

2. “Comprehensions already avoid leakage.”

Comprehensions handle simple cases, but not:

  • multi-statement logic
  • try/with blocks
  • early exits
  • more complex control flow
    The proposal targets the general case.

3. “The semantics seem unclear.”

The intended meaning is:

  • x:: → block-local binding
  • x:: T → block-local binding with type annotation
  • block exit → remove or restore previous value
    This mirrors established constructs in other languages.

4. “Why add a new scope?”

Block scope is standard in most languages.
Python’s current behavior mostly stems from historical constraints.
Opt-in block scoping aligns with common expectations while maintaining full backward compatibility.

5. “This encourages shadowing.”

Python already allows shadowing, often unintentionally.
The proposal makes shadowing explicit, making code safer and easier to reason about.

6. “It adds new syntax.”

:: is minimal, avoids new keywords, and keeps code clear.
It can reduce boilerplate by eliminating helper functions used solely to isolate names.

7. “It complicates the language.”

The intention is to simplify reasoning:

  • today: leakage rules vary across constructs
  • with this: variables marked :: never leak
    Existing behavior remains unchanged.

8. “Typed bindings inside blocks aren’t needed.”

Python’s typing ecosystem increasingly reasons at block-level granularity.
Forms like acc:: float = 0.0 improve clarity and assist static tools.

9. “Context managers can already do this.”

Context managers add runtime behavior, but do not create lexical scopes.
This proposal concerns compile-time name isolation only.

10. “This changes expectations for loops.”

Only when the developer explicitly opts in:

  • for i in ... → unchanged
  • for i:: in ... → block-local variable
    The model mirrors how the walrus operator extended capabilities without altering =.

Summary

The proposal:

  • provides an explicit, optional tool,
  • aligns with modern language design,
  • avoids common scoping pitfalls,
  • complements Python’s typing evolution,
  • and doesn’t break existing code.
1 Like

It is easily readable for most people that know about scoping rules in Python. You can also find a good name for the class to make it more readable. You can’t, in any way, comment on x::, except for changing the variable name, which leaves this unnecessary.

Source? Why would this be any more lightweight and/or explicit than any of the other solutions (mainly classes) already discussed.

Why would one need such functionality in comprehensions? You can easily use functions within the comprehension, and exiting early can be done with some generator.

def gen(l):
     idx = 0
     while cond and idx <= len(l):
          yield l[idx]
          idx += 1
    
[e for e in gen(...) ]

Like? Which established syntaxes from other languages are you talking about? In most languages :: does work with scoping, but only like . in Python, showing where some attribute belongs.

New syntax is not backwards compatible 90% of the time. You’d either need a __future__ import, or have to deal with code working in x.y, not working in 3.14 and below.

Well yeah, Python does allow for shadowing, which can be somewhat useful sometimes. But it’s also not clean code, and discouraged by most Programmers and Style Guides. Making it easier to do name shadowing doesn’t help people to think about naming their variables better (which you can always do, even if you making an API, where you can just rename them in __init__.py). It also does not make code safer in any way, as it encourages something without making that a lot safer to begin with.

How would it keep code clear if you always have to think wether this is used in the context of scoping of slicing. People will always have to learn new syntax, as easy as it may be. Sure, two characters can easily be written, but code is read more often than it is written. Therefore, making it easy to understand is a more important point, than helping the programmer not need to type a few more characters.

Or you avoid name shadowing, which makes it easier for everyone.

I wouldn’t use the walrus operator as an example here. It’s probably the most controversial piece of Python syntax there is.

I. No, see this and similar.
II. By making it easier to shadow names?
III. How would it be ‘completed’.
IV. It does, see my point above.

Basically, you are asking for syntax to solve a problem, which doesn’t need to be solved. Scoping is already explicit enough in Python, if you know what you are doing. If not, just rename your variables (although if you know what you’re doing, renaming might be good too).

Perhaps accepting that there are established ways to do this already, is the best thing we can do right now?