Sharing Context Variables Between Python Functions – Patterns

functional programmingpythonvariables

I am looking for ways of passing around a set of contextual variables that are required by functions.

As a Python programmer, right now I can see three ways of solving the problem: passing them around as explicit arguments, class encapsulation and closures (function factory).

I find that explicit arguments tend to obscure the function logic, class encapsulation tend to lead to the do-it-all, thousand lines classes, whereas closures tend to create an programming overhead because of factory creation.

I was wondering if there were any other patterns that allow sharing of dynamically defined contextual variables between functions devoid of such shortcomings? For instance how this problem would be solved in the context of functional programming?

Best Answer

You might look into the Reader or State monads; they can be used for sharing data between (pure, monadic) functions, without resorting to explicit parameters or the like.

There's a tutorial series on F# for fun and profit about the State monad (AKA Workflow in F# parlence). I also gave a description of it in this SO answer that appears to be fairly approachable.

There are some implementations of other monads in python that you could reference too, if you have to implement your own.

Many people (myself included) find monads challenging at first, so if you're not familiar with the concept then I'd recommend looking at some of the simpler monads like Maybe or Either, and then perhaps Reader or Writer, before tackling State. The Python implementations linked to above may make things more clear for you than what you're likely to find elsewhere online (which would probably be Haskell).

A few caveats: there is still a little overhead over just functions, since the monad is involved, and this has to be used with functions; I don't think you could do this with class methods (though I could be wrong -- I've never tried it!).

UPDATE:

The "monads in python" page/lib I linked to above actually does have a state-monad implementation; check out the code starting at ###### StateChanger Monad ######### for the definition and examples.

EDIT in response to question in comment:

Yes, that's the way you'd do it -- to be extra clear, the arguments you declare the function with should not include the environment variables, just the ones the function would need normally; the environment variables will be accessible through the monadic get and set functions.

A Python example using the notation given in the "monads in python" link above:

@do(StateChanger) # <-- this makes makes it a stateful computation
def my_stateful_function1(arg):
    # do something with `arg` here
    # then put it in the state:
    yield dict_state_set(key, var_from_arg)
    # I believe you can leave out `mreturn` for this

@do(StateChanger)
def my_stateful_function2(arg1, arg2):
    # something with `arg1` here
    # get the state chained via the monad:
    state = yield get_state()
    # do something else stateful:
    yield my_stateful_function1(arg2)
    # do something with `state` dict here
    # return; you have to use `mreturn` instead of `return`:
    mreturn(some_value)

To run this you'd call:

ret_val = my_stateful_function2("hello","world").run(initial_state_dict)

and your return value would be a tuple:

(return_value_of_function, final_state_dict) = ret_val

Remember to use yield when calling other stateful functions.

Admonition: Be careful about modifying the state in your functions since the Python dictionary type is mutable. You can get and set state vars, as well as apply a (lambda) function to them; you may wish to stick with this rather than pulling the state out and mutating it willy-nilly.

Related Topic