Prototype Inheritence in Python

One of Toby's favorite pastimes is to take concepts from one language and realize it in another. In this episode: Toby implements Javascript-style prototype inheritence in Python.


One thing that is super cool about Javascript is the fact that objects are just maps. Not only can you add attributes dynamically at any point, you can also add methods dynamically. In Python, you can add methods dynamically, but normally you'd have to add it into the class. But there are cases when you don't want to bother making a class. This inspired me to see how far I can go in making Python behave more like Javascript.

Introducing prototype.py

prototype.py lets you write code in Python with Javascript-style prototype inheritence semantics. If you are not familiar with prototype inheritence, just read on for now. I will link to some resources at the end. Now, let's see how to use this bad boy. First you import the module:

>>> from prototype import *

To create a constructor(we don't really have classes anymore), you write a function with the @constructor decorator:

>>> @constructor
... def Person(this, first, last):
...   this.firstName = first
...   this.lastName = last
...
>>> Person
<constructor 'Person'>

I am going to use this rather than self and camelCase rather than under_scores for prototype-style code, because, well...Dorothy, we are not in Kansas anymore.

Now you can do:

>>> bird = Person('Charlie', 'Parker')
>>> bird.firstName
'Charlie'
>>> bird.lastName
'Parker'

You can add attributes to the object just like in normal Python. But unlike in normal Python, which would raise an AttributeError when trying to access non-existent attributes, in prototype-land it merely returns None:

>>> print bird.age
None

You can dynamically add a method to the instance just by tagging on a function:

>>> def sing(this):
...   print '%s sings!!' % this.lastName
...
>>> bird.sing = sing
>>> bird.sing()
Parker sings!!

This affects only the bird instance. If you want the method to be added to all instances of Person however, you can add it to the prototype of Person.

>>> Person.prototype.sing = sing
>>> monk = Person('Thelonious', 'Monk')
>>> monk.sing()
Monk sings!!

This works because Person.prototype is the prototype of the monk instance. In code, this means:

>>> monk.__proto__ == Person.prototype
True

When the sing attribute is not found on the monk instance itself, it will follow the __proto__ reference and look it up in its prototype, which is also referred to as its parent. We can manipulate the parent link:

>>> monkJr = Person('T.S.', 'Monk')
>>> monkJr.__proto__ = monk

so that now monkJr inherits all of monk's attributes:

>>> monk.hair = 'black and curly'
>>> monkJr.hair
'black and curly'

The following are some other properties demonstrating the prototype inheritence model:

>>> assert monkJr.constructor == monk.constructor == Person
>>> assert Object.prototype.constructor == Object
>>> assert Person.prototype.constructor == Person
>>> assert Person.prototype.__proto__ == Object.prototype

If this seems confusing, remember that in prototype-land, there are two kinds of things: constructors and instances. The prototype of a constructor is nothing more than an instance of the type that the constructor represents. A new instance returned from calling a constructor will have its parent set to the prototype of the constructor.

Things You Can't Do in Javascript, i.e. the 1+1>2 Effect

Python is a more powerful and flexible language than Javascript, so, why am I dumbbing it down to Javascript's level? To show that I am not trying to do that at all, let me point out that there are at least a couple of really cool things you can do with prototype.py that you can't with Javascript, simply due to the fact that it is in Python.

The first thing is properties. With prototype.py, you can add properties to objects like so:

>>> def getName(this):
...   return '%s %s' % (this.firstName, this.lastName)
...
>>> Person.prototype.name = property(getName)
>>> bird.name
'Charlie Parker'

You can also specify setters and deleters in the way you'd expect:

>>> def setName(this, name):
...   first, last = name.split(' ')
...   this.firstName = first
...   this.lastName = last
...
>>> def deleteName(this):
...   del this.firstName
...   del this.lastName
...
>>> Person.prototype.name = property(getName, setName, deleteName)
>>> bird.name = 'Dizzy Gillespie'
>>> bird.name
'Dizzy Gillespie'
>>> del bird.name
>>> bird.name
'None None'

The second thing is named parameters, keyword parameters and optional parameters. Let's just do an example with keyword parameters. Let's say I just wanted a way to easily create ad-hoc objects as key-value pairs, I could write:

>>> @constructor
... def Data(this, **kwargs):
...   for key in kwargs.keys():
...     setattr(this, key, kwargs[key])
...

Which lets me write:

>>> project = Data(project='prototype.py', language='python')

But, I could also define methods and properties in this way:

>>> file = Data(fileName='prototype.py', fileExt=property(getExt), 
                    read=read, write=write)

Sweet!

A Note About the Implementation

The implementation of prototype.py is only about 60 lines of Python at time of writing, would have gone down to 40 without property support. The code can be found here. 

A big discovery that enabled me to do this was the new module. It allowed me to take a function and make it into an instance method by binding it to an object:

method = new.instancemethod(function, object)

Another note is that I made the design decision that for methods, I chose to store the unbound function rather than the bound method in the __dict__ of the object. I would then bind the function on the fly when it's asked for. This made it very easily to inherit methods because you don't have to worry that the method is really bound to the parent rather than the instance in question.

Further Reading About Prototype Inheritence

If you want to learn more about prototype inheritence, try:

blog comments powered by Disqus