[Python-ideas] 'Injecting' objects as function-local constants
steve at pearwood.info
Mon Jun 13 08:23:38 CEST 2011
Terry Reedy wrote:
> On 6/11/2011 9:30 AM, Jan Kaliszewski wrote:
>> == Use cases ==
>> A quite common practice is 'injecting' objects into a function as its
>> locals, at def-time, using function arguments with default values...
> One problem with trying to 'fix' this is that there can be defaulted args
> which are not intended to be overwritten by users but which are intended to
> be replaced in recursive calls.
I think any solution to this would have to be backward compatible. A big
NO to anything which changes the behaviour of existing code.
>> == Proposed solutions ==
>> I see three possibilities:
>> To add a new keyword, e.g. `inject':
>> def do_and_remember(val, verbose=False):
>> inject mem = collections.Counter()
> The body should all be runtime. Deftime expression should be in the header.
That's not even the case now. The global and nonlocal keywords are in
the body, and they apply at compile-time.
I don't like the name inject as shown, but I like the idea of injecting
locals into a function from the outside. (Or rather, into a *copy* of
the function.) This suggests generalising the idea: take any function,
and make a copy of it with the specified names/values defined as locals.
The obvious API is a decorator (presumably living in functools).
Assume we can write such a decorator, and postpone discussion of any
implementation for now.
Firstly, this provides a way of setting locals at function definition
time without polluting the parameter list and exposing local variables
to the caller. Function arguments should be used for arguments, not
internal implementation details.
def do_and_remember(val, verbose=False):
# like do_and_remember(val, verbose=False, mem=...)
But more importantly, it has wider applications, like testing,
introspection, or adding logging to functions:
return random.choice(alist) + 1
You might not be able to modify my_function, it may be part of a library
you don't control. As written, if you want to test it, you need to
monkey-patch the random module, which is a dangerous anti-pattern.
Better to do this:
def choice(self, arg):
mock = randomchoice_mock()
test_func = inject(random=mock)(my_function)
Because test_func is a copy of my_function, you can be sure that you
won't break anything.
Adding logging is just as easy.
This strikes me as the best solution: the decorator is at the head of
the function, so it looks like a declaration, and it has its effect at
function definition time. But as Terry points out, such a decorator
might not be currently possible without language support, or at least
messy byte-code hacking.
More information about the Python-ideas