I'm writing a utility where I would like to have global variables that change the way a function operates. By default I'd like all the functions to follow one style, but in certain cases I'd also like the ability to force the way a function operates.
Say I have a file Script_Defaults.py with
USER_INPUT = True
In another python file I have many functions like this:
from Script_Defaults import USER_INPUT
def DoSomething(var_of_data, user_input = USER_INPUT):
if user_input:
... #some code here that asks the user what to change in var_of_data
.... # goes on to do something
The problem I face here is that the default parameter only loads once when the file starts.
I want to be able to set USER_INPUT
as False
or True
during the run of the program. To get this behaviour I'm currently using it like this...
from Script_Defaults import USER_INPUT
def DoSomething(var_of_data, user_input = None):
if user_input is None:
user_input = USER_INPUT
if user_input:
... #some code here that asks the user what to change in var_of_data
.... # goes on to do something
This seems like a lot of unnecessary code, especially if I have a lot of conditions like USER_INPUT
, and many functions that need them. Is there a better to get this functionality, or is this the only way?
Using decorators, and manipulation of a function's default arguments, you can use the following solution:
from change_defaults import Default, set_defaults
my_defaults = dict(USER_INPUT=0)
@set_defaults(my_defaults)
def DoSomething(var_of_data, user_input=Default("USER_INPUT")):
return var_of_data, user_input
def main():
print DoSomething("This")
my_defaults["USER_INPUT"] = 1
print DoSomething("Thing")
my_defaults["USER_INPUT"] = 2
print DoSomething("Actually")
print DoSomething("Works", 3)
if __name__ == "__main__":
main()
Which requires the following code:
# change_defaults.py
from functools import wraps
class Default(object):
def __init__(self, name):
super(Default, self).__init__()
self.name = name
def set_defaults(defaults):
def decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
# Backup original function defaults.
original_defaults = f.func_defaults
# Replace every `Default("...")` argument with its current value.
function_defaults = []
for default_value in f.func_defaults:
if isinstance(default_value, Default):
function_defaults.append(defaults[default_value.name])
else:
function_defaults.append(default_value)
# Set the new function defaults.
f.func_defaults = tuple(function_defaults)
return_value = f(*args, **kwargs)
# Restore original defaults (required to keep this trick working.)
f.func_defaults = original_defaults
return return_value
return wrapper
return decorator
By defining the default parameters with Default(parameter_name)
you tell the set_defaults
decorator which value to take from the defaults dict.
Also, with a little more code (irrelevant to the solution) you can make it work like:
@set_defaults(my_defaults)
def DoSomething(var_of_data, user_input=Default.USER_INPUT):
...