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

Eric Snow ericsnowcurrently at gmail.com
Fri Jun 17 20:43:28 CEST 2011


On Fri, Jun 17, 2011 at 9:22 AM, Alex Light <scialexlight at gmail.com> wrote:
>
> Doing things this way however removes one of the chief benefits of the
> inject statement,
> the ability to create multiple versions of the same function with different
> sets of shared data.


The only way I could see runtime injection work is if you limited the
injection to names already tied to the locals, in the same way
parameters are.  This would require one of the 3 solutions that Nick
outlined.

Let's assume the injection values were stored on the function object,
like closures and defaults are, perhaps in an attribute named
__atdef__.  Then runtime injection could be used to replace __atdef__,
like you can with the defaults, if it were not read-only.

However, If __atdef__ were read-only, like __closure__, the runtime
injection would have to do some trickery, like you have to do if you
are going to mess with the closures [1].  This is a hack, in my mind,
since being read-only indicates to me that the expectation is you
shouldn't touch it!

With that said, in that case a runtime injection function would have
to generate a new function.  The new function would have to have the
desired __atdef__, and match any other read-only attribute.  Then the
injection function would copy into the new function object all the
remaining attributes of the old function, including the code object.

Here's an example of what I mean:

  def inject(f, *args):
      if len(args) != len(f.__atdef__):
          raise TypeError("__atdef__ mismatch")
      func = FunctionType(f.__code__, f.__globals__, f.__name__,
                                        f.__defaults__, f.__closure__,
tuple(args))
      # copy in the remaining attributes, like __doc__
      return func

You can already do this with closures and defaults.  If the elements
of __atdef__ are cells, like with __closure__ then you would have to
throw in a little more logic.  If you wanted to do kwargs, you would
have to introspect the names corresponding to __atdef__.  I don't know
if this would have a performance impact on the new function, in case
__defaults__ or __closure__ are more than just attributes on the
function object.

Like I said, this is a hack around the read-only attribute, but it
shows that with the solutions Nick outlined, you can still have an
injection function (could be used as a decorator, I suppose) .  The
only catch is that the names for __atdef__ would be tied into the
function body at definition time, which I think is a good thing.

Finally, regardless of if __atdef__ were read-only or not, I think a
runtime injection function should return a new function and leave the
old one untouched.  That seems to meet the use-case that you
presented.

-eric


[1] Here's an example:

from types import FunctionType
INJECTEDKEY = "injected_{}"
OUTERLINE = "    outer_{0} = injected_{0}"
INNERLINE = "        inner_{0} = outer_{0}"
SOURCE= ("def not_important():",
                 "    def also_not_important():",
                 "    return also_not_important")
def inject_closure(f, *args):
      injected = {}
      source = list(SOURCE)
      for i in range(len(args)):
          source.insert(1, OUTERLINE.format(i))
          source.insert(-1, INNERLINE.format(i))
          injected[INJECTEDKEY.format(i)] = args[i]
      exec("\n".join(source), injected, injected)
      closure = injected["not_important"]().__closure__
      func = FunctionType(f.__code__, f.__globals__, f.__name__,
                                        f.__defaults__, closure)
      func.__annotations__ = f.__annotations__
      func.__doc__ = f.__doc__
      func.__kwdefaults__ = f.__kwdefaults__
      func.__module__ = f.__module__
      return func



More information about the Python-ideas mailing list