The Artima Developer Community
Sponsored Link

Python Buzz Forum
More on Python Metaprogramming

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
Ian Bicking

Posts: 900
Nickname: ianb
Registered: Apr, 2003

Ian Bicking is a freelance programmer
More on Python Metaprogramming Posted: Dec 12, 2005 11:33 PM
Reply to this message Reply

This post originated from an RSS feed registered with Python Buzz by Ian Bicking.
Original Post: More on Python Metaprogramming
Feed Title: Ian Bicking
Feed URL: http://www.ianbicking.org/feeds/atom.xml
Feed Description: Thoughts on Python and Programming.
Latest Python Buzz Posts
Latest Python Buzz Posts by Ian Bicking
Latest Posts From Ian Bicking

Advertisement

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.

Read: More on Python Metaprogramming

Topic: Philip Pullman on Narnia Previous Topic   Next Topic Topic: Narnia

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use