Python Programming, news on the Voidspace Python Projects and all things techie.

Elixir and Just Enough Magic

emoticon:eggs Via my usual Python dripfeed (PlanetPython) I read the announcement about Elixir.

Elixir is a new declarative mapper for SQLAlchemy. It draws inspiration from ActiveRecord (Ruby on Rails) and has what is both a very nice syntax, and an unusual one for Python :

class Director(Entity):
    has_field('name', Unicode(60))
    has_many('movies', of_kind='Movie', inverse='director')
    using_options(tablename='directors')

class Movie(Entity):
    has_field('title', Unicode(60))
    has_field('description', Unicode(512))
    has_field('releasedate', DateTime)
    belongs_to('director', of_kind='Director', inverse='movies')
    has_and_belongs_to_many('actors', of_kind='Actor', inverse='movies')
    using_options(tablename='movies')

class Actor(Entity):
    has_field('name', Unicode(60))
    has_and_belongs_to_many('movies', of_kind='Movie', inverse='actors')
    using_options(tablename='actors')

So how are the classes here configured, when there are only what look like function calls within the class namespaces ?

A peek into the source code reveals the trick.

has_field (to pick an example) is defined in fields.py. They are classes, wrapped inside instances of the Statement class.

When they are called, the call is added to a list of statements, stored as a class attribute Statement.statements.

Entity has a metaclass, which means that when subclass definitions are executed (at the time the module is imported) then Statement.process is called.

Statement.process is a class method, which processes all the statements that have been added to the list (which is then cleared). Because the list is cleared each time process is called [1], statements only contains entries for the class currrently being created. It's not thread-safe, but then importing modules isn't anyway.

This is a very nice trick, not too difficult to work out, but just enough magic. I really like the syntax anyway. I'm not sure I'd use a trick like this myself, but the authors of Elixir have created a nice DSL. (With not a self in sight as Andrzej was quick to point out - oh and the source code is very readable, which is a good sign.)

[1]The metaclass is instantiated, to actually create the class, once the code in its namespace has been executed.

Like this post? Digg it or Del.icio.us it.

Posted by Fuzzyman on 2007-02-13 20:52:33 | |

Categories: ,


Automatic Properties and a Metaclass Conundrum

emoticon:cat I've fixed the (fairly subtle) bug in my properties metaclass (see the previous blog entry).

This means that instead of using the metaclass directly, you can create a class that uses it, and just subclass that :

class WithProperties(object):
    __metaclass__ = __properties__

Any subclasses of WithProperties will have properties automatically created from methods declared with a get_, set_ (etc) prefix [1] :

class TestClass(WithProperties):
    def __init__(self):
        self.__value1 = "A read only value"
        self.__value2 = None

    def get_readonly(self):
        print 'getting readonly attribute'
        return self.__value1

    def get_test(self):
        print 'Getting test attribute'
        return self.__value2

    def set_test(self, value):
        print 'Setting test attribute'
        self.__value2 = value

This started out as a toy implementation, but I think this is actually a nice way of declaring properties. I might even use it...

The original version of the metaclass worked fine, but when you subclassed a class using it, the metaclass wouldn't be called for the subclass. The subclass still had its __metaclass__ attribute set, it just wasn't being used.

The bug was due to the final line of the __new__ method in my metaclass (I've already changed it in the post below).

The offending line was :

return type(classname, bases, newClassDict)

Can you spot what is wrong with this?

It took Christian [2] and me several whole minutes of forehead-wrinkling scrutiny (and a close encounter with some code by Ian Bicking) to work it out.

All classes are types. Metaclasses creates new instances of types, with the specific characteristics that you supply. When a class is created (normally using the class statement), the metaclass is called, which is responsible for creating the new class.

Creating a new class using type(classname, bases, newClassDict) meant that my class was an instance of type - not of my metaclass.

The reason that subclasses still had the __metaclass__ attribute set was because of the normal inheritance rules...

Changing that line to type.__new__(meta, classname, bases, newClassDict) means that the my class really is an instance of the metaclass, and subclassing it works as expected.

>>> class x(object): pass
...
>>> type(x)
<type 'type'>
>>> class y(type): pass
...
>>> class x(object):
...  __metaclass__ = y
...
>>> type(x)
<class '__main__.y'>

I've updated my article on metaclasses to include this.

Oh, my achievement on the book on Saturday was writing minus seven pages of the Python tutorial. Refactoring at work. Smile

[1]The getter and setter methods don't appear in the class namespace.
[2]Xtian has finally posted a new blog entry after months. This is great news as he is one of the most profoundly ridiculous people I know. Razz

Like this post? Digg it or Del.icio.us it.

Posted by Fuzzyman on 2007-02-12 21:18:24 | |

Categories: ,


Painless Properties

emoticon:men Python properties are great. They can provide a clean API with useful functionality.

class Class(object):
    def __init__(self):
        self.__attribute = None

    def __getattribute(self):
        return self.__attribute

    def __setattribute(self, value):
        self.__attribute = value

    def __delattribute(self):
        print "You can't delete attribute."

    attribute = property(
        fget = __getattribute,
        fset = __setattribute,
        fdel = __delattribute,
        doc = "Some docstring"
    )

There are two problems with properties. The syntax is a bit ugly and it leaves your getter and setter methods inside the class namespace.

The hack below is a metaclass which offers one solution to these problems. You don't use property directly, but declare methods that begin with 'get_', 'set_' or 'del_'. You can also have class attribute docstrings that start with 'doc_'. Following 'get_', 'set_', 'del_' or 'doc_' is the property name they belong to.

A class that uses this metaclass will have the appropriate properties created, and the methods used won't appear in the class namespace.

class __properties__(type):

    def __new__(meta, classname, bases, classDict):
        names = set()
        propertyDict = {
            'doc': {},
            'get': {},
            'set': {},
            'del': {}
        }
        newClassDict = {}

        importantNames = set(['del_', 'doc_', 'get_', 'set_'])
        for name, item in classDict.items():
            if name[:4] in importantNames and len(name) > 4:
                propertyName = name[4:]
                names.add(propertyName)
                propertyDict[name[:3]][propertyName] = item

            else:
                newClassDict[name] = item

        for name in names:
            fget = propertyDict['get'].get(name)
            fset = propertyDict['set'].get(name)
            fdel = propertyDict['del'].get(name)
            doc = propertyDict['doc'].get(name)
            newClassDict[name] = property(fget=fget, fset=fset,
                                          fdel=fdel, doc=doc)
        return type.__new__(meta, classname, bases, newClassDict)

class WithProperties(object):
    __metaclass__ = __properties__

To see it in action, create an instance of the following class and access the 'test' and 'readonly' properties. You can also look inside the class namespace using dir(Test) to verify that the getter / setter / etc methods and attributes aren't there. You should also see that type(Test) is __properties__.

class Test(WithProperties):

    def __init__(self):
        self.__test = 3
        self.__readonly = 2

    def get_test(self):
        print 'Getting test'
        return self.__test

    def set_test(self, value):
        print 'Setting test'
        self.__test = value

    def del_test(self):
        print 'Attempting to delete test'

    doc_test = "Docstring for test property"

    doc_readonly = "a read only property"

    def get_readonly(self):
        print 'Getting readonly'
        return self.__readonly

Obviously this is only a toy implementation, caveat emptor. Smile

The only issue that I'm aware of (which is easy to fix if you have the desire) is that it uses sets, so it requires Python 2.4 or greater.

Like this post? Digg it or Del.icio.us it.

Posted by Fuzzyman on 2007-02-11 18:55:43 | |

Categories: ,


Microsoft Invented AJAX (and they wish they hadn't)

emoticon:animals_cat I've just read a very interesting blog entry by Jeff Attwood: Did IE6 Make Web 2.0 Possible?.

By somewhere around 2002 Microsoft had effectively won the browser war. IE 6 was introduced in August 2001. Up until then Microsoft had released a new version of IE every year or eighteen months or so.

XMLHttpRequest, which is central to AJAX, was introduced in IE 5.0 in March 1999 as a proprietary feature.

Conventional wisdom says that Microsoft stopped developing the browser after they won the war because they were scared of the 'web as platform' damaging the market for desktop applications.

Jeff suggests, rather ironically, that by letting the browser market stagnate (so that by 2004 something like 95% of people browsing the internet were using a single browser version) Microsoft made it dramatically easier for people to contemplate writing web applications!

The super-saturation and monoculture of IE6 from 2002 to 2004 created an incredibly rich, vibrant development platform where developers were free to push the capabilities of the browser to its limits. Without worrying about backward compatibility. Without writing thousands of if..else statements to accommodate a half-dozen alternative browsers.

Nice theory.

Personally I think the web still sucks as a platform (anyone using a browser based IDE yet ?) and I don't see much sign of that changing. Nicer web apps are great, but why does everything need to be delivered through a browser ? I think client apps with collaborative features (or other web service integration) are the way to go.

Oh, another interesting thing:

This is also from last year. Slides 36-38 show exactly which features from Python Javascript 2 will borrow. This is iterators, generators and list comprehensions.

Like this post? Digg it or Del.icio.us it.

Posted by Fuzzyman on 2007-02-11 14:45:49 | |

Categories:


Happy Birthday Voidspace

emoticon:pda Voidspace.org.uk is four years old today.

Happy birthday it. Smile

In web years that's practically ancient...

Like this post? Digg it or Del.icio.us it.

Posted by Fuzzyman on 2007-02-11 00:58:58 | |

Categories:


Cats or Dogs, C# or Java

emoticon:python I like James Tauber's new site, Cats or Dogs [1].

It asks you a series of questions, where you choose one thing over another. Then it presents you with some information about other people's choices.

For example :

People who prefer
c# to java
are 3.0 times more likely to prefer
boxers to briefs.

People who prefer
java to c#
are 50% more likely to prefer
briefs to boxers.

This might be significant.

You can view some of the results it generates, here. Can you guess that I'm trying to find a distraction from writing ? Razz

[1]I presume it is written with Django.

Like this post? Digg it or Del.icio.us it.

Posted by Fuzzyman on 2007-02-10 23:12:49 | |

Categories: ,


And Today in the Junk Folder

emoticon:drive Oops... it looks like someone forgot to fill in a few values before firing up their evil spam machine :

%TO_CC_DEFAULT_HANDLER
Subject: %SUBJECT
Sender: "%FROM_NAME" <%FROM_EMAIL>
Mime-Version: 1.0
Content-Type: text/html
Date: %CURRENT_DATE_TIME

%MESSAGE_BODY

Received today. My current favourite is the subject of a piece of spam I received yesterday: The Chronicles of The Rogue Pirate Ninjas: Revised, Act One. Smile

Like this post? Digg it or Del.icio.us it.

Posted by Fuzzyman on 2007-02-10 12:05:55 | |

Categories:


How Vista is Good for Python (well... maybe)

emoticon:baldguy Smart and perceptive people that you are, I'm sure that the recent series of 'Mac vs PC' adverts won't have escaped your notice. Windows Vista is finally out, and for the first time in living memory [1] computer users everywhere are considering a major change to their operating system.

Apple is hoping that some of those people will switch to Mac OS. And why shouldn't they?

Apple's star has been rising of late. The iPod underpinned their financial stability, cheaper hardware have contributed to rising sales and the buzz about Vista has led to mainstream press [2] coverage about how Vista is just Microsoft playing catch-up with Mac OS. The capability of running Windows on Mac hardware is also helping those on the edge of changing to make the jump.

Linux users can take heart from this. A lot of work has gone into making Linux easier to install, and friendlier for non-ultra-geeks to use on the desktop. Distributions like Ubuntu are making this their explicit goal. Some Mac hardware owners run Linux, and if running a non-Microsoft OS on the desktop is becoming fashionable then it can only be good news for Linux.

None of this changes the fact that Microsoft are starting from a pretty good position. Statistically almost every desktop computer in the world is running their operating system. I can't be bothered to look it up, but what is it - still greater than 95% ?

Assuming that more people continue to switch away from Windows, and it certainly looks that way, then eventually we will get close to a magical tipping point.

At the moment it makes financial sense for many software companies, particularly small ones, to develop exclusively for the Windows operating system. It simply isn't worth the investment of time and resources to develop for alternative platforms. At some point it will not only be a nice idea for software firms to make their applications cross-platform, but it will become good business sense - followed not long after by essential business sense. Where is that tipping point ? 15% ? 20% ?

As we approach this point programming languages with a strong history of providing hassle-free cross platform development environments will be ever more important. That includes Python. Smile

Not only that, but cross-platform libraries like wxWidgets and QT will also become more important. This is good news for users of those libraries, whichever language they are consumed from.

[1]Possibly a minor exaggeration.
[2]I mean real world press, not just the stuff us geeks read.

Like this post? Digg it or Del.icio.us it.

Posted by Fuzzyman on 2007-02-10 01:17:54 | |

Categories: ,


Hosted by Webfaction

Counter...