Friday, March 01, 2013

Correction: Javascript NOT more dynamic than Python

EDITED: Adam showed me that I had missed a way to do something similar in Python as I added below.
====


After a very long hiatus, I'm finally back and working on a web version of rur-ple, my "Karel the Robot in Python" program.  Since it's going to be on the web, I have decided to have both a Javascript version and a Python version (using Brython).  As I was exploring javascript prototype-based "inheritance" pattern, I found  what I think is a neat way to illustrate how dynamic the method lookup can be in Javascript, in a way that has (to my knowledge) no parallel in Python.

As in rur-ple, I have a robot "class" called UsedRobot which is broken (i.e. like the original Karel it can only turn left, etc.).  For completely separate reasons, I had decided that I would actually implement a PrivateRobot "class" within the RUR  namespace, which would not be directly exposed to the user, and have UsedRobot "inherit" from it.

Here's what one can can do


var r = new UsedRobot();      // does not have a turn() method
RUR.PrivateRobot.prototype.turn = RUR.PrivateRobot.prototype.turn_left;   // give such a method to the "parent"
r.turn();  // turns left using the new parent's method.
UsedRobot.prototype.turn = RUR.PrivateRobot.prototype.__turn_right;  //give different method to "child"
r.turn();  //  turns right - look up stops at "child"
delete UsedRobot.prototype.turn;
r.turn(); // turns left again  - look up goes up to parent since method no longer exists in child.

I still prefer Python to Javascript ... but I find that one can do neat things in Javascript which (to my knowledge) can not be done in Python.   Caveat: the fact that something like the above can be done in Javascript does not mean that it should be done.

===
Adam, in the comments, pointed out that something similar can be done with Python, using instance.__class__ to modify the behaviour of a class.  For example:


class A:
    pass

class B(A):
    def turn(self):
        print("turn left")

def right(self):
    print("turn right")

b1 = B()
b2 = B()
a1 = A()
b1.turn()  # prints turn left
a1.__class__.turn = right
a1.turn() # prints turn right
b1.turn() # prints turn left
del b1.__class__.turn
b1.turn()  # prints turn right



9 comments:

adam said...

You can certainly do that in Python.

from types import MethodType

class A(object):
def b(self):
print "B"

def c(self):
print "C"

a = A()
a.b = MethodType(c, a)
# prints "C"
a.b()

André Roberge said...

That's different: it's adding a method to an instance. Using the example in my blogpost, with Javascript, if I had say 3 robots (r, s and t), they would all inherit the new method from changing their parent's prototype. Furthermore, I don't believe that you can remove a method from a child class in Python and have a lookup being made from that point to the parent's, as is the case with Javascript.

André Roberge said...

Actually, your approach did not modify the parent class of A (which is object), unlike the example I gave. And, instead of using MethodType, I believe that you could simply have done a.b = c. This is just simple monkeypatching at the instance level - nothing on the lookup chain has been changed.

adam said...

You can

del a.b

to get back access to the original method. You can also

a.__class__.b = c

or

type(a).b = c

to change the method on the class.

You can even replace

A.__bases__

with a new set of base classes if you want to change the type / MRO of A.

adam said...

Also, you can do

class D(object):
def b(self):
print "D"

a.__class__ = D

and completely change the type / class of a.

adam said...

In response to your second comment at 9:25:

You can't just do

a.b = c

if you want "self" to automatically get passed to "c" -- if you just assign the function, it's a static method, not an instance method.

André Roberge said...

Adam, you are certainly correct about not being able to do a.b = c as I wrote - I stand corrected on that.

However, I still maintain that what you are suggesting is doing changes per instance. If one has multiple instances of the same type, I'm quite sure that the type of change you are suggesting would have to be done on every single instance - unlike the case with Javascript.

Perhaps there is a way to do it using metaclasses ... but I would be surprised.

adam said...

I'm sorry if my examples weren't clear, but you can certainly alter a class object, either directly or through an instance, and therefore effect the behavior of all instances of that class.

>>> class A(object):
... def b(self):
... print "C"
...
>>> d = A()
>>> e = A()
>>> d.b()
C
>>> e.b()
C
>>> def f(self):
... print "G"
...
>>> e.__class__.b = f
>>> d.b()
G
>>> e.b()
G

André Roberge said...

Thanks Adam for correcting me.