
Nick Coghlan wrote:
On Mon, Jun 13, 2011 at 5:11 PM, Steven D'Aprano <steve@pearwood.info> wrote:
Function parameters should be kept for actual arguments, not for optimizing name look-ups.
Still, the post-** shared state (Jan's option 2) is likely the most obvious way to get early binding for *any* purpose without polluting the externally visible parameter list.
I wouldn't call adding even more complexity to function signatures "obvious", although I grant that it depends on whether you're Dutch :) Another disadvantage is that it uses a symbol instead of a word. Too many symbols, and your code looks like Perl (or APL). It's hard to google for ** to find out what it means. It's harder to talk about a symbol than a word. (In written text you can just write ** but in speech you have to use circumlocutions or made-up names like double-splat.) [...]
It seems like the path of least resistance to me - the prevalence of the default argument hack means there's an existing, widespread practice that solves real programming issues, but is flawed in some ways (specifically, messing with the function's signature). Allowing declarations of shared state after the keyword-only arguments seems like a fairly obvious answer.
The problem with injecting locals in the parameter list is that it can only happen at write-time. That's useful, but there's a major opportunity being missed: to be able to inject at runtime. You could add test mocks, optimized functions, logging, turn global variables into local constants, and probably things I've never thought of. Here's one use-case to give a flavour of what I have in mind: if you're writing Unix-like scripts, one piece of useful functionality is "verbose mode". Here's one way of doing so: def do_work(args, verbose=False): if verbose: pr = print else: pr = lambda *args: None pr("doing spam") spam() pr("doing ham") ham() # and so on if __name__ == '__main__': verbose = '--verbose' in sys.argv do_work(my_arguments, verbose) But why does do_work take a verbose flag? That isn't part of the API for the do_work function itself, which might be usefully called by other bits of code. The verbose argument is only there to satisfy the needs of the user interface. Using a ** hidden argument would solve that problem, but you then have to specify the value of verbose at write-time, defeating the purpose. Here's an injection solution. First, the body of the function needs a generic hook, with a global do-nothing default: def hook(*args): pass def do_work(args): hook("doing spam") spam() hook("doing ham") ham() # and so on if __name__ == '__main__': if '--verbose' in sys.argv: wrap = inject(hook=print) else: wrap = lambda func: func # do nothing # or `inject(hook=hook)` to micro-optimize wrap(do_work)(my_arguments) If you want to add logging, its easy: just add an elif clause with wrap = inject(hook=logger). Because you aren't monkey-patching the hook function (or, heaven help us, monkey-patching builtins.print!) you don't need to fear side-effects. No globals are patched, hence no mysterious action-at-a-distance bugs. And because the injected function is a copy of the original, other parts of the code that use do_work are unaffected. But for this to work, you have to be able to inject at run-time, not just at write-time. -- Steven