Pythonic UIs

posted: Thu, 13 Aug 2009 18:57 | filed under: / / / | permalink | Tags: , , | Comments: 1

I've just been reading Richard Jones' current project, where he's implementing a very Pythonic way of creating GUIs (for example, managing gui contexts using Python's context managers). I'm very very excited, and I hope this sample code shows why:

with gui.form() as form:
    name = gui.row('Name', gui.text())
    skill = gui.row('Skill level', gui.selection(['Awesome', 'Radical', 'Understated']))
    @gui.submit('OK')
    def on_click(button):
        print 'Got name=%r'%name.value
        print 'Got skill=%r'%form['skill'].value
        gui.stop(0)
    @gui.cancel('Cancel')
    def on_click(button):
        gui.stop(1)

Take a look at what this code does at Richard Jones' weblog. It's pretty awesome.

Cocomo: An experiment in metaprogramming in python

posted: Mon, 01 Jun 2009 11:16 | filed under: / / / | permalink | Tags: , , , , , | Comments: 1

Friday saw the second edition of the UTAS Computing Society Lightning Talks, if you haven't seen them already, I highly recommend that you check them out -- this semester's were at a very high standard indeed, and I wish I'd printed out more certificates for good talks :). My talk was a demonstration of using metaprogramming in Python, though that's not what it seemed to be about.

An introduction

I went to the Apple University Consortium's Cocoa Workshop at the University of New South Wales in February of this year, it was a heap of fun, and we learnt heaps whilst there. One of the key distinguising features of Cocoa is its use of verbose English method and attribute names, the idea being that each line of code should make a reasonable amount of sense when read aloud, hence:

NSString *str = [[NSString alloc] initWithString @"Hello World!"]

does indeed allocate memory to hold a string object, and initialises the newly-allocated memory with a string containing "Hello World!" (this code is highly redundant!). Supposedly such a naming scheme allows coders to write code that is easily maintainable by the original coder, and easily learnable by people who pick up the code for the first time.

On the other hand, my friends, collectively known as Maclab (named after the room at UTAS we inhabit) have developed a rather unique vocabularly, which in particular involves replacing as many words as possible with either 'thrust' or 'fork', so "Thrustingly thrust the forking forker" is not an uncommon utterance amongst my friends. If this is indeed their usual mode of conversation, then Cocoa's way of identifying methods and attributes is not necessarily going to be a particularly intiuitive one. So, clearly, we need a version of cocoa that meets their needs.

The setup

So, conveniently, Apple provide a comprehensive version of the Cocoa API, thanks to the PyObjC project. We can therefore use the Python bindings for Cocoa facilitate our new version of Cocoa. Since Cocoa has a very consistent naming scheme, we can simply perform string replacement to translate from our maclab language to the standard cocoa language, using a routine somewhat like this:

def translate(inp):
	''' Translates an input string from key language to value language '''
	for i in LANGUAGE:
		if i[0].islower():
			# Try both capital case and lowercase
			inp = inp.replace(i, LANGUAGE[i])
			inp = inp.replace(rtitle(i), rtitle(LANGUAGE[i]))
		else:
			inp = inp.replace(i, LANGUAGE[i])
	return inp

def rtitle(i):
	return i[0].upper() + i[1:]

Here, LANGUAGE is a dictionary, with keys in the language code will be written in and values being the target language (in this case, Cocoa). There's not all that much of a sophisticated nature going on in here. Now that we have a method by which we can translate our attribute accesses, we can get to the meat of the the code.

The implementation

To achieve the new API, we need to use a technique that I will call proxying. This involves the use of objects whose sole purpose is to intercept attribute accesses and calls to an underlying object. In this case, the point of intercepting the calls and accesses is to perform translation from our new objects to standard Cocoa objects. In Python we can do this by overriding the standard attribute access and call methods.

First up is __getattr__, the attribute accessor method -- for this, we are passed a string; the name of the attribute that we're looking for, which we translate, and then attempt to access upon the method on the underlying object (in this case, self.__u__). There is one slight hitch: in certain cases, we may not want to translate the attribute name. This is true, in particular, of the attribute that represents the underlying object. Hence we provide a REAL_ATTRS list, for which we use the default __getattr__ method for. This results in code that looks something like this:

	def __getattribute__(self,name):
		#''' Perform method/attribute proxying on ''' + repr(self.__u__)
		if name in REAL_ATTRS:
			return object.__getattribute__(self,name)
		else:
			new_objectname = "self.__u__.%s" % translate(name)
			new_object = eval(new_objectname)
			return CocomoProxy(new_object)

Notice that we use eval to perform the lookup? It turns out that __getattr__ doesn't work universally, whereas . notation does -- so we use that for less failover.

Being able to call methods on the objects is important, but slightly more difficult -- we want behaviour to be maintained, so we need to make sure that proper Cocoa objects are passed as arguments, rather than the Proxy objects that you may have originally dealt with. We can do this with Python's argument unpacking -- we build up a list of arguments, and unproxy them as necessary:

	def __call__(self,*a, **k):
		new_a = [i.__u__ if type(i) == CocomoProxy else i for i in a]
		new_k = dict( (translate(i), k[i].__u__ if type(k[i]) == CocomoProxy else k[i]) for i in k)
		return CocomoProxy(self.__u__(*new_a,**new_k))

We may also need to deal with iterators. This can be done using a standard generator function, thusly:

	def __iter__(self):
		for i in self.__u__:
			yield CocomoProxy(i)

Finally, there may be legitimate reasons for extracting Cocoa objects, these include printing strings, so we provide an accessor method called no_really:

	def no_really(self):
		return self.__u__

And that's the entire implementation! The final thing we need to do is provide a pre-proxied version of the base module for Cocoa. Let's call it GypsyMagic.

The payoff

So now that we have a working bridge from Maclab English to Cocoa English, we can take this sample code that puts some stuff into an array, and then prints it:

import AppKit

hworld = AppKit.NSString.alloc().initWithString_("Hello, World!")
arr = AppKit.NSMutableArray.alloc().init()

arr.addObject_(hworld)
arr.addObject_("Boop!")


for i in arr:
	print i	

And write it in the far more palatable:

from cocomo import GypsyMagic

hworld = GypsyMagic.OGMouthWords.subsume().makeGogoWithMouthWords_("Hello, World!")
arr = GypsyMagic.OGForkableTrinketHolder.subsume().makeGogo()

arr.thrustinglyThrustForker_(hworld)
arr.thrustinglyThrustForker_("Boop!")

for i in arr:
	print i.no_really()

If you're interested in seeing how it all fits together, see Cocomo's website.

Python 3000

posted: Thu, 04 Dec 2008 12:51 | filed under: / / / | permalink | Tags: , | Comments: 0

Python 3000 (aka Python v3.0) has just been released! Grab your source tarballs whilst they're hot!

Fun with Sockets

posted: Tue, 28 Oct 2008 22:06 | filed under: / / / | permalink | Tags: , , | Comments: 2

Whilst doing some coding today for my semester research project I found a need to check for incoming data on a socket without taking any data out of the stream. Here's the code I came up with:

     
cp.sock.setblocking(False)
try:
    cp.sock.recv(0)
    stuffwaiting = True
except socket.error:
    stuffwaiting = False
cp.sock.setblocking(True)

This code works finely on Linux -- you can only receive data if there is data to be received (even if you want to receive no data). Unfortunately, the code doesn't port to Mac OS -- you may receive as many bytes as there are in the socket's buffer -- if there are no bytes in the buffer, you can receive 0 bytes. Therefore, the following fix is necessary:

     
cp.sock.setblocking(False)
try:
    cp.sock.recv(1, socket.MSG_PEEK)
    stuffwaiting = True
except socket.error:
    stuffwaiting = False
cp.sock.setblocking(True)

So, my question for Lazyweb is: is there a better way to do this?

Google Code Jam

posted: Thu, 17 Jul 2008 09:40 | filed under: / / / | permalink | Tags: , , , , | Comments: 0

Just a friendly reminder to you all that Google Code Jam 2008's qualifying round opens today. Code Jam is an individual programming competition, which lets you compete with a number of languages. Qualifying opens at 9AM Australian time, and you have until that time tomorrow to qualify. Good luck!

LCA2009: Python Miniconf Proposal

posted: Wed, 16 Jul 2008 17:20 | filed under: / / / | permalink | Tags: , , , , | Comments: 0

I just posted the following announcement of my proposal for a Python Miniconf to be held at linux.conf.au 2009 to Australian Python mailing lists. I'm posting it here in case anyone has missed it:

Linux.conf.au 2009 is to be held at the University of Tasmania's Sandy
Bay campus in Hobart, Tasmania over the week of January 19-24; and the
call for presentations [1] and mini-confs [2] is now open.

I am currently in the process of producing a proposal for a Python
Miniconf to be held at LCA, so I thought I should detail my plans to a
greater audience for the purpose of feedback/suggestions.

The miniconf would be a single-day conference on the broad topic of
Python programming.  Broadly speaking, the topics I would like to see
presented would range through:
- Recent developments on Python core (presented to a more
Python-oriented audience than may happen at LCA proper)
- Frameworks and libraries (e.g. Django, which I believe is hitting
1.0 this year)
- Techniques of Python programming (e.g. using advanced/new/etc
features of Python effectively)
- Discussions of Python use in the "real world" (e.g. Industry use,
education, etc, etc, etc).
- Anything else Python-related: please make suggestions! [3]

The intention is that there would be 5 "organised" talks of ~45
minutes length (although if there is sufficient interest/free space, I
could split blocks into 2x25 minute talks), with a 50-minute block of
lightning talks to conclude the event, with the possibilty of some
loosely-organised get-together of pythoners after the day's
proceedings have finished.

If you are interested in participating in the Python miniconf (which
requires you to also be interested in attending Linux.conf.au), please
e-mail me [3].  I would particularly like topics of talks that people
would be able to give (vague/general is fine at this early stage in
preparation), so that I can include them in the miniconf proposal (so
the earlier I receive them the better!).

Thanks in advance for any help that you may be able to offer me.

-- Christopher Neugebauer

P.S. if I have missed any user groups/potentially interested parties,
could you please forward this message on -- I've already dealt with
most relevant mailing lists in Australia, but international lists may
also be interested, due to the nature of LCA as an international
conference.

[1] http://marchsouth.org/media/news/6
[2] http://marchsouth.org/media/news/15
[3] for the benefit of google groups users: chrisjrn [ a t ] gmail.com 

TUCS Tech Talk Photos

posted: Mon, 05 May 2008 13:43 | filed under: / / / | permalink | Tags: , , , , , | Comments: 0


TUCS Tech Talk #1, originally uploaded by Christopher Neugebauer.

As I mentioned previously, TUCS had its first tech talk on Friday (delivered by myself, on the topic of Introductory Python), this is the first opportunity to show off photos from it. I was rather impressed by the turnout (there are a few people off to the side that can't be seen in the frame).

Case-insensitive String Replacement (in Python)

posted: Thu, 27 Mar 2008 18:52 | filed under: / / / | permalink | Tags: , , , | Comments: 3

One feature that Python's built-in string replacement facilities does not provide is case-insensitive string replacement. This is a reasonably useful construct (that I use fairly frequently in other languages, such as PHP), which I couldn't find code for after Googling -- so here's some code that does it (licenced under the WTFPL, of course)

import re

def ireplace(self,old,new,count=0):
	''' Behaves like string.replace(), but does so in a case-insensitive
	fashion. '''
	pattern = re.compile(re.escape(old),re.I)
	return re.sub(pattern,new,self,count)
You can also subclass str in order to use it as a bound method:
import re

class str_cir(str):
	''' A string with a built-in case-insensitive replacement method '''
	

	def ireplace(self,old,new,count=0):
		''' Behaves like S.replace(), but does so in a case-insensitive
		fashion. '''
		pattern = re.compile(re.escape(old),re.I)
		return re.sub(pattern,new,self,count)

Python Ugliness

posted: Wed, 13 Feb 2008 23:27 | filed under: / / / | permalink | Tags: , | Comments: 2

UPDATED: The code I posted initially had a slight bug in it, this has since been fixed. There is another bug in this code, which is addressed at the end of the post.

This is part two in a continuing (maybe) series about writing Perverse sorting algorithm implementations in Python -- part one can be found via this link

After discovering the three-line Quicksort, and showing it to a friend of mine, I was challenged to come up with an implementation of Merge Sort in a similar length of time. I was fairly certain that it couldn't be done, until I discovered some really nasty features of Python that probably aren't meant to be exploited in the way that I've done so. And so here's the result (reformatted for line-length issues), followed by a commentary on why this is as damn-awful as I say it is:

def msort2( L ):
	a,b = ((len( L ) > 1 and (msort2(L[:len( L )/2]), msort2(L[len( L )/2:])))
		 or (list( L ), []))
	return ([(lambda m,n: ((len( n ) == 0 and m.pop( 0 )) or 
		(len( m ) == 0 and n.pop( 0 )) or 
		(m[0] < n[0] and m.pop(0)) or 
		(n.pop(0))))(a,b) for i in xrange(len( a )+len( b ))])

There you have it -- merge sort in three lines of Python. Though, in my opinion, it looks a lot more like something a chronic Perl obfuscator would be proud of. So let's investigate the language "features" that I've employed in order to get the code so compact:

The boolean operators

Python's boolean operators are weird. Instead of returning boolean literals, and and or return one of their operands, in the following way:

ABA and B
TrueANYTHINGB
FalseANYTHINGA
ABA or B
TrueANYTHINGA
FalseANYTHINGB

It's easy to convince yourself that this behaviour is correct, if esoteric. Making use of this feature, a and b or c behaves very similarly to C/C++/Java's ternary operator (enough for our purposes). The fact that these operators are shortcircuited allows us our next abuse (although it prevents the sort from being general -- the code can't quite handle lists with zeroes in them).

list.pop() returns the value it removes

Due to Python not allowing arbitrary assignments as expressions, we need a convenient method of both incrementing a list, and getting the front of that same list at the same time. Happily, pop will return the list element that you pop from the list, which provides us with a value to insert into our comprehended list. C++'s queue template from the STL has both a front() method, as well as a pop() method, probably just to avoid travesties like this (pop does not return a value).

List comprehensions are sequential

For this code to work, it absolutely depends on the list comprehension getting evaluated like a for loop -- and thankfully that is the case. If it weren't, then all of the pop() side-effects would fall apart quite terribly.

lambda

Need I say more?

And there you have it -- a strictly non-Pythonic implementation of Merge Sort. Hate mail to the usual address, or if you think you can better it, feel free to comment. Everyone else, take this as an educational exercise in writing readable code.

Small disclaimer: This version is not quite general -- it can't handle lists with zeroes in them: I do have a version which makes use of Python 2.5's ternary operator, but the code was a lot more readable.

Python Prettiness

posted: Sun, 10 Feb 2008 12:45 | filed under: / / / | permalink | Tags: , | Comments: 0

Whilst in Melbourne, and on the recommendation of Anthony Baxter (current Python release manager) I picked up a copy of O'Reilly's Python Cookbook, it's certainly a worthwhile read. In it, I found the most beautiful piece of Python code I've seen in a long time:

def qsort( L ):
	if len( L ) <= 1: return L
	return qsort( [i for i in L[1:] if i <= L[0]] ) + [L[0]] + \ 
               qsort( [i for i in L[1:] if i > L[0]] )

For those of you who don't recognise it, it's an implementation of Quicksort, and a particularly inefficient one, at that. What is nice about it is how it shows off the expressive power of Python's list comprehensions. The example in the Cookbook credits the Haskell Web Site for the inspiration for the code (Haskell, incidentally is where the idea of list comprehensions came from in the first place)

It's worth noting that it's not a piece of code that I'd use in real life, but as an example, it's certainly interesting.

If you're interested in the original recipe from the cookbook, you can find it at the Online Python Cookbook.

Page 1 of 2  >>