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

Steven D'Aprano steve at pearwood.info
Tue Jun 14 02:21:24 CEST 2011


Greg Ewing wrote:
> Steven D'Aprano wrote:
> 
>> 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.
> 
> It's still rather non-obvious what's going on, though. Copious
> commenting would be needed to make this style of coding
> understandable.

I don't think so. The injection happens right at the top of the 
function. True, you need to know what "inject" does, but that's no 
different from any other function. Provided you know that "inject" adds 
a local binding to the function namespace, instead of using a global, 
it's easy to understand what this does:

x = 42

@inject(x=23)
def spam():
     print(x)

Not terribly mysterious. The only tricky thing is that some programmers 
aren't comfortable with the idea that functions are first class objects, 
and so:

@inject(len=my_len)
def spam(arg):
     return len(arg)+1

will discombobulate them. ("What do you mean, len isn't the built-in 
len?") But then again, they're likely to be equally put off by global 
patches too:

len=my_len

def spam(arg):
     return len(arg)+1


Doesn't stop us using that technique when appropriate.


> Also, it doesn't seem to generalise. What if the function in
> question calls other functions, which call other functions,
> which themselves need a verbose option? It seems you would
> need to explicitly wrap all the sub-function calls to pass
> the hook on to them. And what if there is more than one
> option to be hooked? You'd rapidly end up with a nightmarish
> mess.

That's a feature, not a bug!

Patches are *local* to the function, not global. If you want to change 
global state, you can already do it, by monkey-patching the module. We 
don't need a new magic inject function to do that. This is not meant to 
be used for making wholesale changes to multiple functions at once, but 
for localized changes to one function at a time. A scalpel, not a chainsaw.



> Here's another way to approach the problem:
> 
>   class HookableWorker(object):
[...]
> Now you can expand the HookableWorker class by adding more methods
> that all share the same hook, still without anything being global.

Absolutely. And that will still be a viable approach for many things.

But...

* You can only patch things that are already written as a class. If you 
want to add a test mock or logging to a function, this strategy doesn't 
help you because there's nothing to subclass.

* There's a performance and (arguably) readability cost to using 
callable classes instead of functions.

* Nor does it clean up the func(arg, len=len) hack.



-- 
Steven



More information about the Python-ideas mailing list