David Heinemeier Hansson has some comments related to the Snakes
and Rubies
event. I have more I want to say in reaction to that post, and
specifically to his comparison of Django models and Rails models. But
I thought I'd at least start with a simple discussion of what can be
done in Python. This is his Rails example:
class Person < ActiveRecord::Base
belongs_to :project_manager
has_many :milestones
has_and_belongs_to_many :categories
end
How do you implement this in Python? Here's the Django syntax:
class Project(meta.Model):
project_manager = meta.ForeignKey(ProjectManager)
milestones = meta.OneToManyField(Milestone)
categories = meta.ManyToManyField(Category)
Here's the SQLObject syntax:
class Project(SQLObject):
project_manager = ForeignKey('ProjectManager')
milestones = MultipleJoin('Milestone')
categories = RelatedJoin('Category')
To me they all look pretty similar. So maybe it's not such a good
example. But anyway, here would be the direct syntactic port of
ActiveRecord:
class Person(ActiveRecord):
belongs_to('project_manager')
has_many('milestones')
has_and_belongs_to_many('categories')
This is quite hard to implement in Python. There's only two cases I
know of that implement kind of function in a class body --
implements() and advise() in Zope and PEAK respectively.
Doing this involves deep tricks, including putting the interpreter
temporarily into trace mode and changing metaclasses around.
But it's still hard because belongs_to() in Python won't
get the class as an argument (as it does in Ruby, as an implicit
self aka @). And it's yet harder because the class doesn't
exist; classes in Python only come into existance after their bodies
are evaluated.
So the semantic (not syntactic) equivalent of the ActiveRecord code
would be:
class Person(ActiveRecord):
pass
Person.belongs_to('project_manager')
Person.has_many('milestones')
Person.has_and_belongs_to_many('categories')
I think there are some Python ORMs that look like this. Basically you
are running some class methods, and those methods modify the class.
This is not complicated in Python at all, but admittedly it doesn't
look as pretty. Some people have proposed that decorators should be
allowed on classes, at which point this might look like:
@belongs_to('project_manager')
@has_many('milestones')
@has_and_belongs_to_many('categories')
class Person(ActiveRecord):
pass
A minor change here is that belongs_to and friends will probably
not be in the ActiveRecord class. Also, I think if anything this
looks worse than the last example (which unlike this one actually
works). Now I think I'm -1 on class decorators.
Of course, neither SQLObject nor Django use these techniques. They
use attributes to store these relations, because in Python the only
easy annotations you can make on a class are with attributes.
Everything else gets thrown away.
Attributes can be somewhat magic, but there is a limit in Python.
That limit is what descriptors can do, and
basically descriptors can respond to attribute access. They can't
tell the class that they exist (until someone tries to access them),
they never know what attribute name they are bound to, and they don't
know what class they are bound to until they are accessed. In ORMs
this causes some problems, because classes really want to know what
columns they have, and columns want to know what name they were given.
Some systems use a model that's just a bunch of dead data, until a
compilation phase that turns it into Python source code. MiddleKit
does that; Django used to do that but doesn't anymore.
Other systems, SQLObject and now Django, use metaclasses.
Metaclasses can be magic, but they aren't magic here. With a
metaclass you can trigger some code to be run everytime a class with
that metaclass is created (and subclasses inherit the superclass's
metaclass).
In effect, it's like:
class Person(ActiveRecord):
project_manager = belongs_to
milestones = has_many
categories = has_and_belongs_to_many
# Implicitly:
Person.setup_everything()
Where setup_everything looks at all the attributes for "magic"
attributes, and lets those attributes modify the class.
SQLObject doesn't work quite like this, but it's moving that way.
Now SQLObject uses a simple metaclass with a class method __classinit__
that takes the place of setup_everything. It's also (in the svn trunk)
starting to use a convention for those magic attributes. Up until now
SQLObject just knew that *Col values were columns. And then
joins and indexes were also added. I don't want to add any more new
things, so I've made __classinit__ look for any new attributes
with a method to call like __addtoclass__(cls, attr_name), and the
attribute can in turn modify the class however it chooses. I'd rather
it not modify the class too much, but
it's necessary at times. The other thing that's come up is that this
all gets hairy with subclassing (and probably does in Ruby too), and
I've found events to be important. But they are also more complicated
than they should be just to fix things up on subclassing.
But anyway, in the end Python can be pretty much like that
ActiveRecord example, except just slightly inverted, and using a
superclass that supports that kind of programming. I would like for
those specific techniques to become more widespread and idiomatic, and
even better for us all to agree on some simple conventions. But there's
no dramatic differences here from Ruby, just like similar original sources
make clear.