Question

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?

Was it helpful?

Solution

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):
    ...
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top