0

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?

flakes
  • 21,558
  • 8
  • 41
  • 88

1 Answers1

2

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):
    ...
tmr232
  • 1,171
  • 14
  • 23
  • `@set_defaults(globals())` would also work? Maybe you can modify it to use the function globals `f.func_globals`. That would be nice, too. Just a hint: put the cool use case of the solution first and then present the code. – User Feb 21 '14 at 16:50
  • Yes, using `@set_default(globals())` works as well. Just make sure you set your variables in the global scope (`global MY_VAR`). You can build a solution around `f.func_globals`, but that would make it more difficult to declare the arguments. – tmr232 Feb 21 '14 at 17:05
  • I mean that instead of `@set_default(globals())` you just do `@default_global` and use the global stored in the function instead of `globals()`. That could be closer to the question. – User Feb 21 '14 at 18:04
  • You can probably do it, but it would make it harder to tell which variables you want to replace. – tmr232 Feb 21 '14 at 22:32