1

I have a local variable x = "local" which unfortunately shares its name with both a global and a non-local variable. Without changing any of the names, can I access all three values? For x = "global" there is globals(), but what about the non-local variable?

Minimal example which illustrates the issue:

x = "global"
def f(x="nonlocal"):
    def g():
        x = "local"
        print(x)  # same as locals()["x"]
        print(globals()["x"])
        # here I want to print the non-local x
    return g

f()()
finefoot
  • 9,914
  • 7
  • 59
  • 102

3 Answers3

2

I don't get your context that you have to use same name.
Anyway, you can capture outer function's locals as nonlocal variable.

x = "global"


def f(x="nonlocal"):
    nonlocals = locals()

    def g():
        x = "local"
        print(x)
        print(nonlocals['x'])
        print(globals()["x"])

    return g


f()()

output:

local
nonlocal
global
Boseong Choi
  • 2,566
  • 9
  • 22
  • As the docs for [`locals`](https://docs.python.org/3/library/functions.html#locals) read: *The contents of this dictionary should not be modified; changes may not affect the values of local and free variables used by the interpreter.* So how is that useful? In any case you just introduced a new variable `nonlocals` in order to resolve the name clash. How is that different from naming the innermost `x` to e.g. `local_x`? – a_guest Mar 23 '20 at 13:32
  • @a_guest No differences. But OP wants to keep the names. As I said, I don't know the context, so I tried to satisfying the requirement. – Boseong Choi Mar 23 '20 at 13:34
  • @finefoot Since you want to access some value from within the body of the function it means you have control over the source code of that function. So you could simply rename that variable. You say it isn't your code, but then why do *you* want to access non-local variables? – a_guest Mar 30 '20 at 12:47
1

Though you couldn't do this with the code written exactly as given, you can use inspect to get non-local variables. Note the changes to the call and return. If the caller is the outer scope instead of global scope, the previous frame will be f.

import inspect

x = "global"
def f(x="nonlocal"):
    def g():
        x = "local"
        print(x)
        print(globals()["x"])
        print(inspect.currentframe().f_back.f_locals["x"])

    return g()


f()

Output

local
global
nonlocal

This might not help in this specific situation, it really depends on how much control you have over the contents of f. If you don't have control over that, you can also monkey-patch f. A lot depends on context.

Sam Morgan
  • 2,445
  • 1
  • 16
  • 25
0

Edit: I didn't notice that the question specifically asked for this without using nonlocal. Leaving this here in case others find it useful.


I question the rationale behind this, but in Python 3, you can use the nonlocal keyword to access the previous scope, store that before re-declaration, then get it later.

x = "global"
def f(x="nonlocal"):
    def g():
        nonlocal x
        y = x
        x = "local"
        print(x)  # same as locals()["x"]
        print(globals()["x"])
        print(y)
    return g


f()()

Output

local
global
nonlocal
Sam Morgan
  • 2,445
  • 1
  • 16
  • 25
  • I meant that you should not use `nonlocal` keyword in this case. It override `f`'s `x` to `"local"`. – Boseong Choi Mar 23 '20 at 15:28
  • Your answer does exactly the same thing with different syntax, except it's storing the original value of `x` in the outer scope. Given the question requirements, this solution works perfectly, so I'm not sure what you're getting at. Are you arguing that this is non-pythonic? – Sam Morgan Mar 23 '20 at 23:13
  • No, it works differently. try `h = f()`, `h()`, `h()`. Your answer works only first time. – Boseong Choi Mar 24 '20 at 03:44
  • The question didn't ask how to preserve `x` in repeated calls, it asked how to access the outer scope, which is what this does. `nonlocal` is specifically designed to **access** the outer scope, please read [PEP 3104](https://www.python.org/dev/peps/pep-3104/) – Sam Morgan Mar 25 '20 at 04:47
  • The word "access" only appears in title and ruby example in the document. You can access outer scope variable without `nonlocal` statement. You only need that re-assign outer scope variable. – Boseong Choi Mar 25 '20 at 05:38
  • Please read [reference](https://docs.python.org/3/reference/simple_stmts.html#the-nonlocal-statement) – Boseong Choi Mar 25 '20 at 05:39
  • If it didn't give local access to nonlocal variables, `y = x` wouldn't function. Neither would `print(x)`. I'm still not sure what you're trying to argue, seems more like semantics than anything else. `nonlocal` is used here with the intent it was designed for. – Sam Morgan Mar 25 '20 at 07:33
  • Wow, can't believe I missed that in the title! – Sam Morgan Mar 29 '20 at 15:36
  • Added another answer that might help. Thanks for prompting me. – Sam Morgan Mar 29 '20 at 18:11