Shorter global and nonlocal syntax

Hi. I did find that this was brought up once, more than a year ago, but only got one comment, and I believe further discussion may be relevant. I thought to make this as a PEP and as per the tutorial, I write here first.

The concept is to shorthand global and nonlocal along with their first usage in a certain scope.

Suggested syntax:

# without this syntax

g = 0

def fn():
    nl = 0
    
    def foo():
        global g
        g += 1
        
        nonlocal nl
        nl += 1
        return nl
    
    def set_global(val):
        global g
        g = val
    
    def read_nonlocal():
        nonlocal nl
        return nl
    
    use_my_callables(foo, set_global, read_nonlocal)

# with this syntax

g = 0
def fn():
    nl = 0
    
    def foo():
        global g += 1
        nonlocal nl += 1
        return nl
    
    def set_global(val):
        global g = val
    
    def read_nonlocal():
        return nonlocal nl
    
    use_my_callables(foo, set_global, read_nonlocal)


# even shorter - allows concise, short lambdas
g = 0
def fn():
    nl = 0
    
    # let's skip foo this time,
    # as it can't be shorterned to a lambda
    
    use_my_callables(
        # set_global, short lambda,
        # using this idea combined with walrus operator
        lambda val: (global g := val),
        
        # read_nonlocal, short lambda
        lambda: (nonlocal nl)
    )

Note that whenever you use the “nonlocal” or “global” statement in existing Python, you’re (almost?) certainly going to use it for a read and/or write, so it stands to reason that this has use-cases, especially when you need to use callables for arguments (see the case above), for example with asynchronous programming and/or callbacks. This is similar to how C-family languages support declaration and first assignment in the same command (int x = 3;), which Python, since adopting optional typing, also allows (x: int = 3) - those are features that allow to unite two parts that often are used together.

Also note how functions whose only purpose is to read and return a nonlocal/global, and functions whose only purpose is to set such variables, can now be written as a lambda, thus making the code shorter and hopefully more understandable - if you have a need for a parameter that is a function that can only be defined as a lambda if this syntax is available, then it’s easier to follow if the function is defined as a lambda, where it’s needed, self-describing by code, rather than by defining an inner def() whose purpose you only see later in the code, and only use once.

While some may note that if you have such a use-case, it may be better solved by just writing classes and using attributes and instance functions (or by other solutions), it also stands to reason that if globals and nonlocals exist as a construct in Python, we may as well make usage for them easier to write and read.

What are your thoughts?

I’d especially like to hear from you if your opinion falls into one of those:

  1. I use nonlocals and globals often, and I would use this
  2. I use nonlocals and globals often, but I wouldn’t use this
  3. I don’t use nonlocals and globals, but this would make me more likely to use them
  4. I’m opposed to nonlocals and globals, even in quick-and-dirty code, and this would only make people more likely to use these features that I oppose

But whatever your opinion on this, if you have one, do let me know.

Best regards!

1 Like

I am generally in camp 4, but I am not fully opposed to this, however, I am strongly opposed to the suggestion to allow this on read: That allows it’s usage inside of expressions, which are easy to miss and it has a side effect for the whole function, which are important to not overlook.

I am generally ok with allowing the assignment on the same line as the “declaration”, as long as it’s the first usage of that name within the function.

I think allowing this inside of lambda’s by amending the := assignment operator is going to encourage overly condensed code: lambdas (IMO) generally should not have side effects, and this syntax extension just further encourages that, but I am not as negative on this as allowing it on reads.

read_nonlocal doesn’t need anything fancy - you only need declarations when you assign to something. So what you’re asking for is a way to combine a global/nonlocal statement with an assignment statement.

IMO trying to cram this into a lambda function is a bad idea; lambda functions aren’t normally going to be doing assignment, and having one that exists to assign to a global or nonlocal is unnecessary. In the rare situations where this is necessary, just use a ‘def’ function instead.

That would cut the proposal down to something pretty straight-forward. Interestingly, this was actually part of the original proposal for the nonlocal statement: PEP 3104 – Access to Names in Outer Scopes | peps.python.org and was initially endorsed by GvR, but subsequently not: [Python-Dev] Whatever happened to 'nonlocal x = y'? So it’s something that has been discussed for a long time.

5 Likes

I don’t use nonlocal or globals often but every now and then they serve a need. I don’t think this feature would make me or others more or less likely to use them.

Probably I would have preferred it if the global/nonlocal statement was combined with the assignment and restricted to simple or simple-augmented assignment (e.g. a += 1 but not a[0] += 1). I would have wanted the keyword to appear on each line that assigns to be clear that it modifies each of those assignments:

a = 1

def foo():
    global a = 2
    func2()
    global a = 1

I think that makes it clearer that it is the assignment statements that are modified by the global keyword. The global-ness of each assignment is clearly seen at the point of assignment.

Otherwise we have:

def foo():
    global a
    a = 2
    func2()
    # Lot's of stuff...
    a = 1

Here the global statement radically alters the meaning of the a = 1 line that is far away in the code (nonlocal code effects…).

2 Likes

If the problem this proposal is aiming to solve is that when reading a function code one must remember which variables are “special”, i.e. were declared global or nonlocal, then I would prefer a simpler solution: allow to repeat global/nonlocal as a kind of reminder. Also alter the style guides not to insist on putting global and nonlocal at the top when the function body is larger.

Example (currently it fails with a SyntaxError):

def x():
    global a
    a = 1
    func1()
    a = 2
    ...
    ...

    global a    # repeated for the case somebody reads just this section
    a = 3 
    func(2)
    ...

   # global a
   a = None
   return False

It’s true that the subsequent declarations are basically just comments and # global a would have the same effect, but look again what a difference syntax highlighting makes.

Are you sure about the SyntaxError? I just tried this in 3.8 (the latest one I have readily available) and it works fine. If it fails now it must have been changed since then.

That’s strange. I tried it on Python 3.8 and got this:

Python 3.8.9 (tags/v3.8.9:a743f81, Apr  2 2021, 11:10:41) [MSC v.1928 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> def x():
...     global a
...     a = 1
...     global a
...
  File "<stdin>", line 4
SyntaxError: name 'a' is assigned to before global declaration

Similarly for later versions.

1 Like

I tried all five versions on my PC from 3.8 to 3.12 and got the error. I have 3.8.19.

I agree it would be reasonable to allow a second global declaration as long as it’s consistent with what came before.