>>> 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.
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)
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: