Home Home > 2009 > 04 > 29 > Ruby style method injection in Python
Sign up | Login

Ruby style method injection in Python

April 29th, 2009 by

Ruby has a nice, but very dangerous feature called open classes. That means you can extend any class definition if you want it.

This example shows it

#!/usr/bin/ruby
class Foo
    def do_foo
        return "foo"
    end
end

aFoo = Foo.new()

puts aFoo.do_foo()

class Foo
    def do_bar
        return "bar"
    end
end

puts aFoo.do_bar()

That means you dynamically extend the definition of Foo class, so the last line prints “bar”.

Python behavior is different, because it does not allows this extending. Python just redefines a class Foo, so new instance of Foo will have a do_bar method only. But this not affected an existing ones, like Ruby does.

class Foo:

    def do_foo(self):
        return "foo"

aFoo1 = Foo()

class Bar(Foo):

    def do_nothing(self):
        pass

print issubclass(Bar, Foo)
print isinstance(aFoo1, Foo)

class Foo:
    
    def do_bar(self):
        return "bar"

aFoo2 = Foo()
aBar  = Bar()

print issubclass(Bar, Foo)
print isinstance(aFoo1, Foo)
print isinstance(aFoo2, Foo)

print dir(aFoo1)
print dir(aFoo2)
print dir(aBar)

But what about method injection? My solution is based on ideas of Recipe 81732: Dynamically added methods to a class.

import types

def funcToMethod(func, instance, method_name=None):
    cls = instance.__class__ if type(instance) != types.TypeType else instance
    setattr(cls, \
            method_name or func.__name__, \
            types.MethodType(func, instance, cls))

And that’s all. The funcToMethod bounds func to instances’s class and allows under method_name (default name is a same as a function one). So lets do some testing.

class Foo(object):

    def list(self):

        return [meth for meth in dir(self) if meth[:3] == 'do_' and type(getattr(self, meth)) == types.MethodType]

    def do_bar(self):
        
        return "bar"

def do_foo(inst):

    return "foo"

class Bar(Foo):

    def do_nothing(self):
        pass

aBar = Bar()

aFoo1 = Foo()
print aFoo1.list()
# calling it with class instead of instance is also possible and is equivalent
#funcToMethod(do_foo, Foo, "do_foo")
funcToMethod(do_foo, aFoo1, "do_foo")

aFoo2 = Foo()
print aFoo1.list()
print aFoo2.list()

print aFoo1.do_foo()
print aFoo2.do_foo()

print aBar.list()
print aBar.do_foo()

When you run the code, you will see that funcToMethod adds a new method to Foo class and this changes both existing and new instances as Ruby does too. And subclassing is not a problem, they are be affected too.

Both comments and pings are currently closed.

14 Responses to “Ruby style method injection in Python”

  1. darix

    the real fun with open classes starts if you uses method aliases to extend existing methods. :)

  2. Nice post, but I really don’t understand this notion that open classes are dangerous. Any language constructs are dangerous if you use them in a way that’s dangerous. I really like open classes and one of the things I desperately miss in Python. Thanks again.

    • Michal Vyskocil

      I like Python philosophy “explicit is better than implicit”. So those implicit adding of new methods which looks like normal class declaration* is uncommon. At least for those of us which are not familiar with Ruby. And I am, because that was my first “hello world” in Ruby :-).

      * Yes, I know that definitions/declaration does not make a sense in dynamic languages, but I’m still thinking in those sentences.

  3. This is a similar method, but also works for class methods and fake instance methods: http://svn.colorstudy.com/home/ianb/recipes/magicset.py

    • Michal Vyskocil

      Interesting, I didn’t tested it with class (and static) methods, because I don’t need it, so thanks for the link.

  4. Alexander

    This actually shows off Ruby as the nicer of the two.

    • Michal Vyskocil

      I don’t think that this is a best example, because this implicit behavior of open classes is against Python design rules. Better example should be in method naming, when method? returns boolean and method! modify an instance. In Python is obviously not clear which method do what. So in this case is Ruby more “pythonic” (means explicit) than Python itself :).

  5. Simon Davy

    hmm – both languages allow excactly the same thing with different syntax – python is more explicit, ruby is more implcit, as per normal

  6. Joachim Werner

    What about this?


    class Foo:
    def foo(self):
    return 'foo'

    aFoo = Foo()
    aFoo.foo()

    def bar(self):
    return 'bar'

    Foo.bar = bar

    aFoo.bar()

    It does just the same as the initial Ruby example, but IMHO makes it much more obvious that I’m adding a new method to an existing class.

  7. Joachim Werner

    Another try. Seems like the code tag is not what makes my code snippet show up as code:

  8. Joachim Werner

    class Foo:
    def foo(self):
    return ‘foo’

    aFoo = Foo()
    aFoo.foo()

    def bar(self):
    return ‘bar’

    Foo.bar = bar

    aFoo.bar()

    • Michal Vyskocil

      I thought I got something like unbound method error in this case, but your example runs well.

      • Nicolas

        You’d get unbound method if you tried to call the method as if it were a static method, i.e. straight off the class … in this case Foo.bar().

        if that is indeed what you wanted to do… you have to make it a class method … (by either @classmethod in python 2.4+ or simply bar = classmethod(bar) after its definition in the class)

  9. Joachim Werner


    class Foo:
    def foo(self):
    return ‘foo’

    aFoo = Foo()
    aFoo.foo()

    def bar(self):
    return ‘bar’

    Foo.bar = bar

    aFoo.bar()