Hmm. Now you've got me thinking about the most ergonomic way do define a block in python. Maybe a no-op context manager? So you could do "with scope():"
Ignoring any future/proposed syntax, I think the most block-like would be a local function definition which you then call once right afterward. This is the same you would do in any LISP-like language.
It could take zero arguments if you just want to temporarily extend the current scope with a few more variable-binding statements. Or, it could also have arguments if you want to do a "let" like construct to rename some expression results in the outer (calling) scope during the invocation.
# ... outer scope
x = expr1
y = expr2
r1 = None
r2 = None
def block1():
nonlocal r1
z = expr3
r1 = expr4
block1()
def block2(x, y):
nonlocal r2
z = expr5
r2 = expr6
block2(expr7, expr8)
It could take zero arguments if you just want to temporarily extend the current scope with a few more variable-binding statements. Or, it could also have arguments if you want to do a "let" like construct to rename some expression results in the outer (calling) scope during the invocation.
Edited for typos, code formatting, and bugs ;-).