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.
the real fun with open classes starts if you uses method aliases to extend existing methods. 🙂
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.
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.
This is a similar method, but also works for class methods and fake instance methods: http://svn.colorstudy.com/home/ianb/recipes/magicset.py
Interesting, I didn’t tested it with class (and static) methods, because I don’t need it, so thanks for the link.
This actually shows off Ruby as the nicer of the two.
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 :).
hmm – both languages allow excactly the same thing with different syntax – python is more explicit, ruby is more implcit, as per normal
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.
Another try. Seems like the code tag is not what makes my code snippet show up as code:
I thought I got something like unbound method error in this case, but your example runs well.
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)
class Foo:
def foo(self):
return ‘foo’
aFoo = Foo()
aFoo.foo()
def bar(self):
return ‘bar’
Foo.bar = bar
aFoo.bar()