Monday, May 09, 2011

Adding Methods without Inheritance

Did you know that C# will let you add methods to objects, without needing to write new sub-classes? I didn't until today. The following snippet adds a new method to generic list instances, called Shuffle, which randomises the items in the list.
static class ExtensionMethods {

public static void Shuffle(this IList list)
{
var rand = new System.Random();
int n = list.Count;
while (n > 1) {
n--;
int k = rand.Next(n + 1);
T value = list[k];
list[k] = list[n];
list[n] = value;
}
}

}

Is this possible in Python? I think it is, but i need to experiment with some code first... stay tuned.

Update:

Of course you can do this in Python, but it won't work on built in types.
>>> import random
>>>
>>> x = range(10)
>>> def shuffle(self):
... random.shuffle(self)
...
>>> list.shuffle = shuffle
Traceback (most recent call last):
File "", line 1, in
TypeError: can't set attributes of built-in/extension type 'list'
>>> class List(list): pass
...
>>> n = List(x)
>>> List.shuffle = shuffle
>>> n.shuffle()
>>> n
[5, 3, 7, 4, 2, 9, 1, 6, 0, 8]
>>>

5 comments:

beetlefeet said...

Just FYI (no agenda etc) ruby is like the king of this (monkey patching).

Ruby already has array.shuffle so here's an example of breaking it:

irb(main):001:0> l = [5, 3, 7, 4, 2, 9, 1, 6, 0, 8]
=> [5, 3, 7, 4, 2, 9, 1, 6, 0, 8]
irb(main):002:0> class Array
irb(main):003:1> def shuffle
irb(main):004:2> self.sort.reverse
irb(main):005:2> end
irb(main):006:1> end
=> nil
irb(main):007:0> l.shuffle
=> [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

Basically you just redefine stuff on the fly in any class. Hilarity ensues. You can also add new methods of course.

Rafe Kettler said...

This exists in several languages (Ruby and JS, most notably). It's called monkey patching, and it's very bad.

It's not great for existing objects (what's wrong with just subclassing, amirite?), but it's particularly terrible for builtin types (which is where most of the issues in Ruby and JS crop up). I'm glad that Python won't allow monkey patching builtins.

James said...

This can be done fairly easily in a number of ways in Python using types and binding:


>>> import types
>>> import random
>>>
>>> def shuffle(self):
... return random.shuffle(self)
...
>>> class ShuffleList(list):
... """List with shuffle method"""
...
>>> x = ShuffleList(range(10))
>>> x
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> setattr(x, "shuffle", types.MethodType(shuffle, x))
>>> x.shuffle()
>>> x
[5, 1, 6, 0, 8, 3, 2, 4, 7, 9]
>>>

You could also extend the ShuffleClass by binding a new function to it so that new instances will have that new method. (not shown)

cheers
James

GM said...

This is _not_ monkey patching. You can't change existing methods. It's just syntactic sugar that lets you write x.f() instead of f(x).

Unfortunately that also means you can't use it to make an existing class implement a new interface, which would have fixed one of C#'s major extensibility gaps.

-Greg

beetlefeet said...

It isn't all bad. Sure, like most 'tricky' things you can get yourself in big trouble, and it can be abused in nasty ways. But surely that's not a good reason to never use it?

Rails/ActiveSupport uses a lot of this kind of thing. Who doesn't love "is_recent = (timestamp > 5.minutes.ago)"

It's also good for things like stubbing out methods while running unit tests. Or maybe for example hooking certain method calls to print performance testing debug messages or something.

Popular Posts