Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Setting a functools.partial as an instance method in Python

I'm using functools.partial to create a closure, and using setattr to make is callable from a class instance. The idea here is to create a set of methods at runtime.

#!/usr/bin/python
from functools import partial

class MyClass(object):

    def __init__(self, val):
        self.val = val

    @classmethod
    def generateMethods(self):
        def dummy(conf1, self):
            print "conf1:", conf1
            print "self.val:", self.val
            print

        for s in ('dynamic_1', 'dynamic_2'):
            closed = partial(dummy, s)
            setattr(self, "test_{0}".format(s), closed)

It seems to me that partial would bind the current value of s to dummy's first arg, which would free up self to be passed when this is called from an instance.

It's not working how I'd expect

if __name__ == '__main__':
    # Dynamically create some methods
    MyClass.generateMethods()

    # Create an instance
    x = MyClass('FOO')

    # The dynamically created methods aren't callable from the instance :(
    #x.test_dynamic_1()
    # TypeError: dummy() takes exactly 2 arguments (1 given)

    # .. but these work just fine
    MyClass.test_dynamic_1(x)
    MyClass.test_dynamic_2(x)

Is it possible to dynamically create methods which are closures, but callable from instances of the class?

like image 525
ajwood Avatar asked Jul 16 '15 23:07

ajwood


2 Answers

I think the new functools.partialmethod is for this exact use case.

Straight from the docs:

>>> class Cell(object):
...     def __init__(self):
...         self._alive = False
...     @property
...     def alive(self):
...         return self._alive
...     def set_state(self, state):
...         self._alive = bool(state)
...     set_alive = partialmethod(set_state, True)
...     set_dead = partialmethod(set_state, False)
...
>>> c = Cell()
>>> c.alive
False
>>> c.set_alive()
>>> c.alive
True
like image 149
dcmorse Avatar answered Oct 12 '22 23:10

dcmorse


The issue is that when you're calling them using the instances they are actually not bound methods, i.e they have no knowledge about the instance. Bound methods insert the self to the arguments of the underlying function automatically when called, it is stored in the __self__ attribute of bound method.

So, override __getattribute__ and see if the object being fetched is an instance of partial type or not, if yes, convert it to a bound method using types.MethodType.

Code:

#!/usr/bin/python
from functools import partial
import types


class MyClass(object):

    def __init__(self, val):
        self.val = val

    @classmethod
    def generateMethods(self):
        def dummy(conf1, self): 
            print "conf1:", conf1
            print "self.val:", self.val
            print

        for s in ('dynamic_1', 'dynamic_2'):
            closed = partial(dummy, s)
            setattr(self, "test_{0}".format(s), closed)

    def __getattribute__(self, attr):
        # Here we do have access to the much need instance(self)
        obj = object.__getattribute__(self, attr)
        if isinstance(obj, partial):    
            return types.MethodType(obj, self, type(self))
        else:
            return obj


if __name__ == '__main__':
    MyClass.generateMethods()

    x = MyClass('FOO')

    x.test_dynamic_1()
    x.test_dynamic_2()
like image 29
Ashwini Chaudhary Avatar answered Oct 13 '22 01:10

Ashwini Chaudhary