creating a partial-like object with dynamic arguments

Viewed 534

I'm trying to create a partial function but with dynamic arguments that are stored as class attributes and changed accordingly. Something like the following code:

from functools import partial

def foo(*args, msg):
    print(msg)

class Bar:
    def __init__(self, msg):
        self.msg = msg
        self.functions = dict()
        self.functions['foo'] = partial(foo, msg=self.msg)

    def foo_method(self, *args):
        return self.functions['foo'](*args)

b =Bar('1')
b.foo_method()
b.msg = '2'
b.foo_method()

Only, of course, both statements will print '1' as the partial object fixes the arguments. The only alternative I found was changing the attribute to a property and manually changing the partial attributes with the setter:

class Bar:
    def __init__(self, msg):
        self._msg = None
        self.functions = dict()
        self.functions['foo'] = partial(foo)
        self.msg = msg

    def foo_method(self, *args):
        return self.functions['foo'](*args)

    @property
    def msg(self):
        return self._msg

    @msg.setter
    def msg(self, msg):
        self._msg = msg
        self.functions['foo'].keywords['msg'] = msg

I would like to know if there is a more "pythonic" / efficient way to do this, since I really don't need to use properties except for this workaround.

3 Answers

You can use lambda instead of partial for deferred (or often referred to as "lazy") evaluation of the arguments, so that self.msg is not evaluated until the function is called:

class Bar:
    def __init__(self, msg):
        self.msg = msg
        self.functions = dict()
        self.functions['foo'] = lambda *args: foo(*args, msg=self.msg)

    def foo_method(self, *args):
        return self.functions['foo'](*args)

What's wrong with just storing a reference to the passed function and constructing the call on the spot? i.e.:

class Bar:

    def __init__(self, msg):
        self.msg = msg
        self.foo = foo  # a reference to foo, not needed here but used as an example

    def foo_method(self, *args):
        return self.foo(*args, msg=self.msg)  # or just: foo(*args, msg=self.msg)

The simplest possible way I can think of would be just constructing a dict and passing it double-starred to the function to unpack.

Something like:

def some_func(msg, some_arg=None):
    print("Hello world") # ignore the msg for now

call_args = {}
call_args['some_arg'] = 2 # single field
call_args.update({'msg': 1, 'stuff': [2,3,4]}) # multiple at once

some_func(**call_args)

Right now, some_func will throw a TypeError because we've passed more args than the function takes. You could work around this either by having the function accept **kwargs in the signature, trimming down the arguments you don't expect or some other approach.

For now, continuing the last session:

call_args = {'msg': 'abc'} # let's get rid of those extra args
some_func(**call_args) # => prints 'Hello world'
Related