katriel
katriel

Reputation: 205

Shadow a global variable with a modified copy

Please note: this is not a question about how to change a global variable inside a function body. I understand the global keyword.

My script has a bunch of global configuration variables. I want write a function that shadows one of those global variables in the local namespace (called modified_procedure() below) and calls another function that refers to the configuration variable. I.e.,

PARAMETER = 1

def procedure():
    return PARAMETER * 3

def modified_procedure():
    PARAMETER += 1
    return procedure()

This fails because PARAMETER occurs in the body of modified_procedure() so the interpreter considers it a local variable and doesn't look it up in the global namespace. I'm not trying to change the global variable PARAMETER; I am trying to shadow it in modified_procedure()'s namespace.

I can think of a couple of inconvenient solutions:

Can I do it by shadowing PARAMETER? If so, how?

Upvotes: 3

Views: 1955

Answers (3)

Anshul Goyal
Anshul Goyal

Reputation: 76927

Slightly hackish, but you can use the approach below to make things work.

First, define the inital method as below:

PARAMETER = 1

def procedure(PARAMETER = PARAMETER):
    return PARAMETER * 3

Now, in the second method, you can use the globals() method and call it with the modified PARAMETER:

def modified_procedure_v1():
    PARAMETER = globals()["PARAMETER"] + 1
    return procedure()

You can even call the first method from within a modified method using the updated PARAMETER value in following manner:

def modified_procedure_v2():
    PARAMETER = globals()["PARAMETER"] + 1
    return procedure(PARAMETER)

And so, the end result becomes:

>>> modified_procedure_v1() #uses global PARAMETER
3
>>> modified_procedure_v2() #uses PARAMETER value local to this method
6

Upvotes: 4

chepner
chepner

Reputation: 531625

You could (ab)use the mock library to temporarily change the value of PARAMETER as recorded in procedure's global scope:

import mock

def modified_procedure():
    with mock.patch.dict(procedure.func_globals, PARAMETER=PARAMETER+1):
        return procedure()

In Python 3, where mock is part of the unittest package instead of a third-party library, it would be

from unittest import mock

def modified_procedure():
    with mock.patch.dict(procedure.__globals__, PARAMETER=PARAMETER+1):
        return procedure()

Without using mock, you could try something like

def modified_procedure():
    # Support Python 2 and 3. 
    try:
       d = procedure.__globals__
    except AttributeError:
       d = procedure.func_globals
    # Ensure that procedure()'s globals are restored
    # even if it raises an exception.
    try:
       d['PARAMETER'] += 1
       return procedure()
    finally:
       d['PARAMETER' -= 1

Upvotes: 1

user395760
user395760

Reputation:

What you're asking for amounts to dynamic scoping. Python, like most languages still used today (notable exceptions being Perl and Emacs Lisp) does not support dynamic scoping at all, opting for lexical scoping.

You could alter the procedure function object to replace its __globals__. But this would be even more hacky, and more code to boot. If you can change procedure to accept a parameter, do that. If it must refer to a global, temporarily altering the global is going to be the least painful and astonishing approach.

Upvotes: 1

Related Questions