ActiveState Code

Recipe 81732: Dynamically added methods to a class


Ruby has the functionality of being able to add a method to a class at an arbitrary point in your code. I figured Python must have some way for allowing this to happen, and it turned out it did. The method is available instantly to all already existing instances and of course ones yet to be created. If you specify method_name then that name is used for the method call.

One thing to make sure to do is that the function has a variable for the instance to be passed to (i.e. self).

Python
1
2
3
4
5
6
7
8
def funcToMethod(func,clas,method_name=None):
    """Adds func to class so it is an accessible method; use method_name to specify the name to be used for calling the method.
    The new method is accessible to any instance immediately."""
    func.im_class=clas
    func.im_func=func
    func.im_self=None
    if not method_name: method_name=func.__name__
    clas.__dict__[method_name]=func

Discussion

Why would one use this? I am not completely sure. You could use it as a way to get around having to deal with inheritence. But as I said in the summary, this was mainly to show that it was possible and how flexible Python is.

Comments

  1. 1. At 3:11 a.m. on 15 oct 2001, Alex Martelli said:

    module new may be preferable. By binding or re-binding attributes of object func, this recipe is fragile -- if funcToMethod is called twice on the same func, confusion is quite possible. Module new is designed to help exactly with such dynamic needs:

    def funcToMethod(func,clas,method_name=None):
        import new
        method = new.instancemethod(func,None,clas)
        if not method_name: method_name=func.__name__
        clas.__dict__[method_name]=method
    

    A somewhat mind-boggling (or mind-expanding:-) extra of this approach is that func can in fact be any callable, such as an instance of any class that supplies a __call_ special method, or a bound-method...

    The 'instancemethod' function name may be slightly misleading: it generates both bound and unbound methods, depending on whether the second argument is None (unbound) or an instance of the class that is the third argument. See http://www.python.org/doc/current/lib/module-new.html for all the details (there's not much more to it than this, though).

  2. 2. At 6:10 p.m. on 17 oct 2001, Brett Cannon (the author) said:

    Should Have Read the Docs. This is what happens when you get too excited about writing code and don't bother to plan far enough ahead to read the documentation.

    Thank you for pointing out the more proper way of handling this, Alex.

  3. 3. At 2:24 p.m. on 19 oct 2002, Peter Shannon said:

    Using a function wrapper to automatically provide self to methods added at runtime. The problem with only adding a new function to a class instance's dictionary, is it must always be called in a special manner - it dosn't behave in the same way as normal methods. Using a wrapper class can get round this problem providing a more transparent solution.

    The function to be added is say(). The parameter host performs the same purpose as what we normally refer to as self - the object which this method works on. The name host simply reminds us we are parasites in this place!

    #!/usr/bin/env python
    
    def say(host, msg):
       print '%s says %s' % (host.name, msg)
    
    def funcToMethod(func, clas, method_name=None):
       setattr(clas, method_name or func.__name__, func)
    
    class transplant:
       def __init__(self, method, host, method_name=None):
          self.host = host
          self.method = method
          setattr(host, method_name or method.__name__, self)
    
       def __call__(self, *args, **kwargs):
          nargs = [self.host]
          nargs.extend(args)
          return apply(self.method, nargs, kwargs)
    
    class Patient:
       def __init__(self, name):
          self.name = name
    
    if __name__ == '__main__':
       jimmy = Patient('Jimmy')
       transplant(say, jimmy, 'say1')
       funcToMethod(say, jimmy, 'say2')
    
       jimmy.say1('Hello')
       jimmy.say2(jimmy, 'Good Bye!')
    
  4. 4. At 1:30 a.m. on 6 aug 2006, Greg Anderson said:

    Synchronizing instance methods. Slight modification to the recipe "9.1 Synchronizing All Methods in an Object." Whereas the the former synchronizes all instances of a class on a single lock, this code synchronizes each instance on it's own lock to makes sure that only one thread at a time has access to certain bound methods. Unsynchronized methods behave normally. Plus, I think this version is more clear.

    import threading

    Just as it is in recipe 9.1 of Python Cookbook, v2

    def wrap_callable(any_callable, before, after, new_name=None): """ Wrap any callable with before/after calls """ def _wrapped(args, *kwds): before() try: return any_callable(args, *kwds) finally: after() _wrapped.__name__ = new_name and new_name or any_callable.__name__ return _wrapped

    if __name__ == '__main__': import threading, time

    class A(object):
        def __init__(self):
            self.lock = threading.Lock()
            # Synchronize these on self.lock, and make them unbound/bound methods
            A.foo = wrap_callable(A._foo, self.lock.acquire, self.lock.release)
            A.bar = wrap_callable(A._bar, self.lock.acquire, self.lock.release)
        def _foo(self):
            print 'I am foo'
            time.sleep(2)
        def _bar(self):
            print 'I am bar'
        def baz(self):
            print 'I am baz'
    a = A()
    
    print A._foo, A.foo
    print a._foo, a.foo
    print a._foo.__name__, a.foo.__name__
    print type(a._foo), type(a.foo)
    
    
    threading.Thread(target=a.foo).start()
    time.sleep(0.2)
    threading.Thread(target=a.bar).start()
    time.sleep(0.2)
    threading.Thread(target=a.baz).start()
    
  5. 5. At 11:11 p.m. on 22 dec 2008, timepass man said:

    simple way

    class Foo:

    def __init__(self):
        self.x = "x = 1"
        self.y = "y = 2"
    
    def showx(self):
        print self.x
    

    def showy(self): print self.y

    if __name__ == '__main__':

    # Attach at runtime
    Foo.showy = showy
    
    f2 = Foo()
    f2.showx()
    f2.showy()
    

Sign in to comment