[Python-ideas] 'Injecting' objects as function-local constants

Steven D'Aprano steve at pearwood.info
Tue Jun 14 01:55:26 CEST 2011


Jim Jewett wrote:
> On Mon, Jun 13, 2011 at 10:33 AM, Steven D'Aprano <steve at pearwood.info> wrote:
>> Nick Coghlan wrote:
>>> On Mon, Jun 13, 2011 at 5:11 PM, Steven D'Aprano <steve at pearwood.info>
>>> wrote:
> 
>>>> Function parameters should be kept for actual arguments, not for
>>>> optimizing name look-ups.
> 
> Even the bind-it-now behavior isn't always for optimization; it can
> also be used as a way of forcing stability in case the global name
> gets rebound.  That is often an anti-pattern in practice, but ... not
> always.

Acknowledged. But whatever the purpose, my comment still stands: 
function arguments should be used for arguments, not for their 
side-effect of injecting a local variable into the function namespace.



>> 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.
> 
> Using the function object as a namespace (largely) gets around that,
> because you can use a with statement to change the settings
> temporarily.

You mean something like this?

with make_logging_len() as len:
     x = some_function_that_calls_len()


That's fine for some purposes, but you're still modifying global state. 
If some_function_that_calls_len() calls spam(), and spam() also contains 
a call to len, you've unexpectedly changed the behaviour of spam. If 
that's the behaviour that you want, fine, but it probably isn't.

There are all sorts of opportunities for breaking things when patching 
globals, which makes it somewhat of an anti-pattern. Better to make the 
patched version a local.



>> 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:
> 
> [A verbose mode -- full example below, but the new spelling here at the top]
> 
> Just replace:
> 
>> def do_work(args):
>>    hook("doing spam")
>>    spam()
>>    hook("doing ham")
>>    ham()
> 
> with:
> 
>     def do_work(args):
>         __function__.hook("doing spam")
>         spam()
>         __function__.hook("doing ham")
>         ham()
[...]
> (The reason this requires a variant of 3130 is that the name do_work
> may itself be rebound, so do_work.hook isn't a reliable pointer.)

Ah, that's why it doesn't work for me! :)

Even if it did work, you're still messing with global state. If two 
functions are using do_work, and one wants a print hook, and the other 
wants a logging hook (or whatever), only one can be satisfied.

Also this trick can't work for optimizations. A call to do_work.hook 
requires a global lookup followed by a second lookup in the function 
object namespace, which is not as fast as using a local.



-- 
Steven



More information about the Python-ideas mailing list