Search code examples
pythondynamicmethodsnamed-parameters

Dynamically adding key-arguments to method


I would like to set the default key-arguments of an instance method dynamically. For example, with

class Module(object):
    def __init__(self, **kargs):
        set-default-key-args-of-method(self.run, kargs)  # change run arguments
    def run(self, **kargs):
        print kargs

We would have:

m = Module(ans=42)

m.run.im_func.func_code.co_argcount  # => 2
m.run.im_func.func_code.co_varnames  # => ('self','ans','kargs')
m.run.im_func.func_defaults          # => (42,)
m.run()                              # print {'ans':42}

I tried something with types.CodeType (which I don't really understand) for a function (not a method) and got it to work (well not-to-fail), but the added key-arguments did not show in the kargs dictionary of the function (it only print {})

The change has to be done for the current instance only. Actually, I am using a class right now (I'm OO in my mind) so I would like to do it with a class method, but a function is maybe better. Something like:

def wrapped_run(**kargs):
    def run(**key_args):
        print key_args

    return wrap-the-run-function(run, kargs) 

run = wrapped_run(ans=42)

run.func_code.co_argcount  # => 1
run.func_code.co_varnames  # => ('ans','key_args')  ## keep the 'key_args' or not
run.func_defaults          # => (42,)
run()                      # print {'ans':42}

Any advise or idea is welcome.

A little on the context:

The Module class is some kind a function wrapper, which can be use to include the lower-end function in a dataflow system automatically but add intermediate procedures. I would like the module run function (actually, it will probably be it's __call___ function) to have the correct API in order for the dataflow system to nicely generate the correct module's input transparently.

I'm using python 2.7


Solution

  • For the sake of closure, I give the only solution that was found: use exec (proposed by mgilson)

    import os, new
    
    class DynamicKargs(object):
        """
        Class that makes a run method with same arguments
        as those given to the constructor
        """
        def __init__(self, **kargs):
            karg_repr = ','.join([str(key)+'='+repr(value) \
                                  for key,value in kargs.iteritems()])
            exec 'def run(self,' + karg_repr + ',**kargs):\n    return self._run(' + karg_repr + ',**kargs)'
    
            self.run = new.instancemethod(run, self)
    
        def _run(self, **kargs):
            print kargs
    
    # this can also be done with a function
    def _run(**kargs):
        print kargs
    
    def dynamic_kargs(**kargs):
        karg_repr = ','.join([str(key)+'='+repr(value) for key,value in kargs.iteritems()])
        exec 'def run(' + karg_repr + ',**kargs):\n    return _run(' + karg_repr + ',**kargs)'
        return run
    
    
    # example of use
    # --------------
    def example():
        dyn_kargs = DynamicKargs(question='ultimate', answer=42)
        print 'Class example \n-------------'
        print 'var number:', dyn_kargs.run.im_func.func_code.co_argcount
        print 'var names: ', dyn_kargs.run.im_func.func_code.co_varnames
        print 'defaults:  ', dyn_kargs.run.im_func.func_defaults
        print 'run print: ', 
        dyn_kargs.run()
        print ''
    
        dyn_kargs = dynamic_kargs(question='foo', answer='bar')
        print 'Function example \n----------------'
        print 'var number:', dyn_kargs.func_code.co_argcount
        print 'var names: ', dyn_kargs.func_code.co_varnames
        print 'defaults:  ', dyn_kargs.func_defaults
        print 'run print: ', 
        dyn_kargs()
    

    The example function prints:

    Class example 
    -------------
    var number: 3
    var names:  ('self', 'answer', 'question', 'kargs')
    defaults:   (42, 'ultimate')
    run print:  {'answer': 42, 'question': 'ultimate'}
    
    Function example 
    ----------------
    var number: 2
    var names:  ('answer', 'question', 'kargs')
    defaults:   ('bar', 'foo')
    run print:  {'answer': 'bar', 'question': 'foo'}
    

    However:

    • there might be problem if arguments value are not well represented by their repr
    • I think it is too complicated (thus not pythonic), and personally, I did not use it