In this post I will show you how to pass data to places where you cannot pass it as arguments.
As an example, let’s say we have a function that we cannot change which calls
our code (MyAction.action
). Because we cannot change
function_i_cannot_change
, we cannot make it pass dynamic_value
through to
our code. Also note that we cannot pass something else than a number as the
arg
parameter because of its implementation. (Yes, in this simple example
there would be alternative solutions, see note at the end.)
We can solve this by passing the data through a global variable.
Yes, you’re right… Using globals is bad. It makes your code hard to follow and breaks thread-safety.
To address the threading issue, we can use a threading.local
instead:
This solves threading issues, but we still have to know the thread local in the
consuming code and we have to trust all consumers to be well-behaved concerning
what they set on DYNAMIC_TL.value
and that they reset it to the original
value.
Let’s use a context manager to make the usage more safe and clear for the consumers.
I also added get_dynamic_value
to hide the usage of the thread local from the
implementation of action
, too.
This sounds a bit crazy, does anybody really do this?
Yes. Have you ever wondered how
flask.request
works?
Can I really use this?
Well, I would say: Yes, if there is no simpler alternative. The pattern comes with the general problem of global variables:
a) They are available everywhere and thus people will starting using them in more and more places. This leads to unintended coupling.
b) The dataflow is not obvious. You will get surprising and hard to debug errors.
For me, this means that I only use the pattern in a very explicit way that makes sure it will be used responsibly. I would move the thread local and it’s accessors to their own module and make the thread local itself private.
I often find myself using this technique in tests, when I need to get 3rd party code to cooperate. In production code I would try very hard to find an alternative.
Note: Alternative solutions for the simple example
The simple example has a couple pretty simple, preferable solutions,
E.g. there is partial application of functions:
Or a function that returns a function:
Or a class: