
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:
1. 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. @inject(mem=collections.Counter()) 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: def my_function(alist): 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: class randomchoice_mock: def choice(self, arg): return 0 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. -- Steven