6

Let's say that I have a class defined in moduleA.py which I want to add a method to, using some sort of loader method that takes a the name of a second module and the method defined there that should be bound

class ClassA(object):
    def __init__(self,config):
        super(ClassA, self).__init__()

        self.a = 1
        self.b = 2
        self.meth1 = self. bind_method(config)

    def bind_method(self,config):
        # load method
        <return method defined in config as a str 'moduleB.meth2'>

    def calling_method():
        return self.meth1() 

where the method defined in moduleB.py looks something like:

def meth2(self):
    return self.a + self.b

The point being that I want to be able to write meth2 to be able to access class variables of ClassA once it is bound. This way, when you would have something like:

from moduleA import ClassA

A = ClassA()
aout = A.calling_method()

Calling A.calling_method() properly calls the method defined in moduleB.py.

I've seen this sort of binding done in answers on SO after ClassA is instantiated using types.MethodType, but I haven't been able to dig up how to bind inside the class definition so that it is done internally when the class is instantiated.

Any suggestions on what should go in the bind_method method would be much appreciated.

JoshAdel
  • 57,369
  • 23
  • 130
  • 131
  • What role does `config` play here? As `ClassA` is currently defined, the third line in `moduleB.py` would cause an error... – senderle Jan 24 '12 at 00:48
  • @senderle - `config` is maybe a dictionary that has an item defining where the method to be bound is located, or something like that. All that matters is that is specifies the module and method therein that should be added to `ClassA` – JoshAdel Jan 24 '12 at 00:50
  • I'm confused, are you trying to implement class inheritance? – Joel Cornett Jan 24 '12 at 01:35

4 Answers4

6

Skipping the config stuff which wasn't clear to me, the binding itself would look like this:

from moduleB import meth2
ClassA.meth1 = meth2

The important part is that you're binding to the class, not to an instance. This way if you call meth1 on an instance, it will automatically receive the instance as the first argument.

yak
  • 8,009
  • 1
  • 27
  • 21
  • I'm familiar with that type of monkey patching, but those semantics won't work in my case – JoshAdel Jan 24 '12 at 02:04
  • 1
    @JoshAdel, in that case, I think you should change the title to "Dynamically bind method to class instance in python." This is probably the correct answer to the actual question you've asked. – senderle Jan 24 '12 at 02:11
6
import sys
import types

def getobj(astr):
    """
    getobj('scipy.stats.stats') returns the associated module
    getobj('scipy.stats.stats.chisquare') returns the associated function
    """
    try:
        return globals()[astr]
    except KeyError:
        try:
            return __import__(astr, fromlist=[''])
        except ImportError:
            modname, _, basename = astr.rpartition('.')
            if modname:
                mod = getobj(modname)
                return getattr(mod, basename)
            else:
                raise

class ClassA(object):
    def __init__(self, methpath):
        super(ClassA, self).__init__()
        self.a = 1
        self.b = 2
        self.meth1 = types.MethodType(getobj(methpath), self)

a = ClassA('moduleB.meth2')
print(a.meth1())
# 3
unutbu
  • 711,858
  • 148
  • 1,594
  • 1,547
  • +1. The only thing about this is that it doesn't supply a value for `meth1.im_class`. I was working on a similar solution that involved creating a `classmethod` like so: `def bind_method(cls, instance, func)`, that returns a `MethodType`. That seemed like the most elegant way to ensure that the class gets bound correctly as well as the instance... – senderle Jan 24 '12 at 02:09
  • @senderle in looking into this I came across `.im_class` but didn't got it to work correctly. I would be interested in seeing your solution as well – JoshAdel Jan 24 '12 at 02:15
  • @unutbu Thanks, this seems to work and does something like what I tried (`types.Method(,self)`), but I was botching my version of `str_to_obj`. I need to work through your implementation of that helper method, since there are some things in there that I don't immediately understand. – JoshAdel Jan 24 '12 at 02:17
  • 1
    @senderle: You can set `meth1.im_class` by supplying a third argument to `types.MethodType`: `types.MethodType(str_to_obj(methpath), self, type(self))`. However, Python3 removes the third argument from `types.MethodType`, and does not check that the instance is of the right class. So for the sake of Python3 compatibility, I thought it best to drop the third argument. – unutbu Jan 24 '12 at 02:20
  • @unutbu, yes, I didn't spell out my point completely; supplying that third argument was exactly what I meant to suggest. But dropping it makes sense, in light of what you say -- I didn't realize that Python 3 removes that parameter. Good to know, thanks! – senderle Jan 24 '12 at 02:40
3

Since meth2() is a function, it is a descriptor and you can bind it by calling the __get__() method.

def meth2(self):
    return self.a + self.b

class ClassA(object):
    def __init__(self,config):
        super(ClassA, self).__init__()
        self.a = 1
        self.b = 2
        self.meth1 = config.__get__(self, ClassA)

c = ClassA(meth2)
print c.meth1() #correctly prints 3 
jimifiki
  • 4,779
  • 1
  • 28
  • 53
-2

There's actually a much simpler way to do this:

class ClassA(object):
    def __init__(self,config):
        super(ClassA, self).__init__()

        self.a = 1
        self.b = 2

    from moduleB import meth2 as meth1

    def calling_method():
        return self.meth1()
marcusljx
  • 887
  • 7
  • 8