Richard Jones' Log Richard Jones' Log: Python

Mon, 15 Mar 2010
A simple asyncore "echo server" example

Tonight I wanted a simple asyncore echo server example for my lecture notes and couldn't immediately find one. Doug Helman has a more complex one in his asyncore Module Of The Week post, but I wanted Just The Basics. So I read up a little and wrote one :-)

First the server:

import asyncore, socket

class Server(asyncore.dispatcher):
    def __init__(self, host, port):
        asyncore.dispatcher.__init__(self)
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.bind(('', port))
        self.listen(1)

    def handle_accept(self):
        # when we get a client connection start a dispatcher for that
        # client
        socket, address = self.accept()
        print 'Connection by', address
        EchoHandler(socket)

class EchoHandler(asyncore.dispatcher_with_send):
    # dispatcher_with_send extends the basic dispatcher to have an output
    # buffer that it writes whenever there's content
    def handle_read(self):
        self.out_buffer = self.recv(1024)
        if not self.out_buffer:
            self.close()

s = Server('', 5007)
asyncore.loop()

And the client:

import asyncore, socket

class Client(asyncore.dispatcher_with_send):
    def __init__(self, host, port, message):
        asyncore.dispatcher.__init__(self)
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.connect((host, port))
        self.out_buffer = message

    def handle_close(self):
        self.close()

    def handle_read(self):
        print 'Received', self.recv(1024)
        self.close()

c = Client('', 5007, 'Hello, world')
asyncore.loop()

When run the server should display:

Connected by ('127.0.0.1', 56757)

(or similar - the port number will vary) and the client should display:

Received 'Hello, world'

It was actually pretty easy to write, and kinda fun.

category: Python | permanent link
Fri, 05 Mar 2010
10th Python Game Programming Challenge in three weeks

The 10th Python Game Programming Challenge (PyWeek) is coming. It'll run from the 28th of March to the 4th of April.

The PyWeek challenge:

  1. Invites entrants to write a game in one week from scratch either as an individual or in a team,
  2. Is intended to be challenging and fun,
  3. Will hopefully increase the public body of game tools, code and expertise,
  4. Will let a lot of people actually finish a game, and
  5. May inspire new projects (with ready made teams!)

In honor of the 10th challenge I've finally brought the website out of the 80s :-) ...

BeforeAfter
category: Python | permanent link
Mon, 22 Feb 2010
Melbourne Python Users Group

The next meeting of the Melbourne Python Users Group (MPUG) will be next week, Monday the 1st of March at 6:30PM at Horse Bazaar.

Full info on the meeting including directions and talks at the MPUG wiki page.

See you there!

category: Python | permanent link
Fri, 05 Feb 2010
PyWeek 10 (10!) is coming

The 10th Python Game Programming Challenge (PyWeek) is coming. It'll run from the 28th of March to the 4th of April.

The PyWeek challenge:

  1. Invites entrants to write a game in one week from scratch either as an individual or in a team,
  2. Is intended to be challenging and fun,
  3. Will hopefully increase the public body of game tools, code and expertise,
  4. Will let a lot of people actually finish a game, and
  5. May inspire new projects (with ready made teams!)

If you've never written a game before and would like to try things out then perhaps you could try either:

  1. The tutorial I presented at LCA 2010, Introduction to Game Programming, or
  2. The book Invent Your Own Computer Games With Python
category: Python | permanent link
Sun, 24 Jan 2010
Linux.Conf.Au 2010 is done

What a week. Unlike other week-long conferences I've been to this one had talks every day. Combined with the usual events every night I'm quite tired now :)

I gave two presentations: a State of Python talk which was a little rambling but otherwise well-received.

I did the haka. Much fun!

Even better-received was my tutorial Introduction to Game Programming which was attended by both programmers and non-programmers. Pia Waugh and Pamela Fox* both have good writeups. A couple of the attendees have come to me afterwards to say again how they enjoyed the tutorial and are continuing to develop their games. Quite a good outcome, I think!

On Wednesday night I got my solderin' on and put together a Pebble board for my Arduino. It's a neat invention of Luke Weston and the builds were made possible by the folk from the Melbourne Connected Community HackerSpace.

During the week I set up the PyCon Au website with the help of Benno Rice, Tim Ansell and Michael Kedzierski. It's running the PyCon-Tech code which Malcolm Tedinnick has offered to bring into the modern age of Django post-1.0 :)

Finally on Saturday I organised a Python booth at the Open Day in Wellington Town Hall. I had a lot of great chats with a diverse range of people including some younger boys and girls who were excited by the game programming aspect of Python. Following advice (I've lost a link to) I had a donated external monitor (thanks Nic) which made showing things off much easier. I had a nice 1.8m banner printed (thanks PSF) which looked cool and gave out about 250 stickers :)

* Google Wave account required

category: Python | permanent link
Fri, 22 Jan 2010
withgui finally made public!

On the heels of the release of the cool withhacks library, I've finally (thanks Robert Collins for helping) released withgui on Launchpad. withgui is my experiment in simple GUI creation as mentioned previously (see also.)

I'll be looking at incorporating withhacks to replace my less-elegant (and fragile) namespace hackery. Feel free to take the code, branch and run. Contact me if you're interested in contributing (no comments on this blog but you'll figure out how to.)

Thanks also to Brianna Laugher who tried to help me release to Launchpad a couple of months ago - the failure then was in my available time.

category: Python | permanent link
Fri, 15 Jan 2010
New "html" module release, 1.8

I just had a query regarding my Python html generation module, asking whether I could a) upload a ZIP file and b) whether it was Python 3 compatible. I made one change (map(...)list(map(...))) and have released version 1.8 on PyPI.

category: Python | permanent link
Python module statement ordering ... wart?

Can't call this one a "wart" because it's present in Python 3 and we all know Python 3 got rid of all the warts <wink>.

It has to do with the very specific ordering of statements at the top of your Python source. Specifically if you have any of the following they must appear in this order:

  1. Top-of-file comments (Typically "# /usr/bin/env python", file encoding or source control revision ID string), then
  2. Module documentation string, then
  3. "from __future__ import ..." statement(s), then
  4. The rest of your module.

Failure to order them precisely like this will result in one of a couple of things: either your docstring will not be created correctly (as the __doc__ attribute) or you'll get a SyntaxError because your "from __future__ import ..." statements aren't "at the beginning of the file" (even though they can't be at the beginning of the file.) If the docstring appears after the "from __future__" imports it won't be used as __doc__.

category: Python | permanent link
Tue, 05 Jan 2010
New Year's Python meme
  1. What’s the coolest Python application, framework or library you have discovered in 2009 ?
    promise - just too cool :)
  2. What new programming technique did you learn in 2009?
    I learnt how to use some of the new features in Python 2.6 and 3.1 but mostly I was just refining my knowledge of Twisted and SMS protocols.
  3. What’s the name of the open source project you contributed the most in 2009? What did you do?
    Roundup - I still lead development of the project even though there's usually one or two other code contributors. I also released html and failed to release withgui ;)
  4. What was the Python blog or website you read the most in 2009?
    Planet Python.
  5. What are the three top things you want to learn in 2010 ?
    For my day job I have to learn about SS7 and MAP. I imagine I won't have much room for anything else, though I do look forward to random presentations at the Melbourne Python Users' Group :)
category: Python | permanent link
Tue, 22 Dec 2009
Python's Deficient Generators

I've had people try to explain why Python's generators are deficient before and the light just went off. To flatten an arbitrarily deep (though stack-limited) nested iterator structure, I wrote:

>>> def flatten(iterable):
...  for elem in iterable:
...   if hasattr(elem, '__iter__'):
...    for sub in flatten(elem): yield sub
...   else:
...    yield elem
... 
>>> list(flatten([[1,2,3],[[2,3],4],[4,5,6]]))
[1, 2, 3, 2, 3, 4, 4, 5, 6]

The key failing is that inner "for" loop. It's called "trampolining" as described by David Beazley. If I could somehow yield to the top of the generator stack it'd be unnecessary. It'd look something like PEP 380:

>>> def flatten(iterable):
...  for elem in iterable:
...   if hasattr(elem, '__iter__'):
...    yield from flatten(elem)
...   else:
...    yield elem
... 
>>> list(flatten([[1,2,3],[[2,3],4],[4,5,6]]))
[1, 2, 3, 2, 3, 4, 4, 5, 6]

Too bad there's a moratorium on changes :)

Updated to include the PEP, thanks ulrik!

Tue, 15 Dec 2009
Sharing my "What's New In Python" presentations

I've put together presentations for describing the new features in Python versions 2.4, 2.5 and 2.6. I did this because my employer, like many companies using Python, has only just installed 2.6 - previously almost all development was done using 2.3 (with just a little 2.4, hence I included that version's information as a refresher.)

The slides are on slideshare. Download the Keynote originals though 'cos slideshare burnt the presenter notes onto the slides.

I'll be uploading "What's new in Python 3" some time in late January.

category: Python | permanent link
Thu, 17 Sep 2009
Simple, elegant HTML generation, released

I've just released my "simple, elegant HTML generation" module, "html.py"

See, I do release stuff :)

And astute readers might notice I've "released often" as well :)

category: Python | permanent link
Wed, 16 Sep 2009
Simple, elegant HTML generation

OK, I looked. I searched. I didn't find. So here you go...

from cgi import escape
class HTML(object):
    '''Easily generate HTML.

        >>> h = HTML()
        >>> p = h.p('hello, world!')
        >>> p.text('more text')
        >>> with h.table(border='1', newlines=True):
        ...     for i in range(2):
        ...         with h.tr:
        ...             h.td('he<l>lo', a='"foo"')
        ...             h.td('there')
        ... 
        >>> print h
        <p>hello, world!more text</p>
        <table border="1">
        <tr><td a="&quot;foo&quot;">he&lt;l&gt;lo</td><td>there</td></tr>
        <tr><td a="&quot;foo&quot;">he&lt;l&gt;lo</td><td>there</td></tr>
        </table>

    '''
    def __init__(self, name=None, stack=None):
        self.name = name
        self.content = []
        self.attrs = {}
        # insert newlines between content?
        self.newlines = False
        if stack is None:
            stack = [self]
        self.stack = stack
    def __getattr__(self, name):
        # adding a new tag or newline
        if name == 'newline':
            e = '\n'
        else:
            e = HTML(name, self.stack)
        self.stack[-1].content.append(e)
        return e
    def text(self, text):
        # adding text
        self.content.append(escape(text))
    def __call__(self, *content, **kw):
        # customising a tag with content or attributes
        if content:
            self.content = map(escape, content)
        if 'newlines' in kw:
            # special-case to allow control over newlines
            self.newlines = kw.pop('newlines')
        for k in kw:
            self.attrs[k] = escape(kw[k]).replace('"', '"')
        return self
    def __enter__(self):
        # we're now adding tags to me!
        self.stack.append(self)
        return self
    def __exit__(self, exc_type, exc_value, exc_tb):
        # we're done adding tags to me!
        self.stack.pop()
    def __str__(self):
        # turn me and my content into text
        join = '\n' if self.newlines else ''
        if self.name is None:
            return join.join(map(str, self.content))
        a = ['%s="%s"'%i for i in self.attrs.items()]
        l = [self.name] + a
        s = '<%s>%s'%(' '.join(l), join)
        if self.content:
            s += join.join(map(str, self.content))
            s += join + '</%s>'%self.name
        return s

There was an escaping error in this blog post (the < and > around the "l" first in "hello") which I didn't catch before Planet Python grabbed my feed, so it's wrong on there but correct here. Hrm.

Also, look, ma! A ternary expression! My first in Python!

Did you like "join.join"? Heh. I know...

category: Python | permanent link
Sun, 23 Aug 2009
Python's 9th write-a-game-in-a-week challenge

Theme voting for the latest (9th!) write-a-game-in-a-week PyWeek challenge is on!

Everyone should write a game and this is the perfect venue for doing so - you've only got a week (a lot of game ideas can be written in a weekend) and there's lots of help available. And it's a lot of fun!

So get to it!

category: Python | permanent link
Wed, 19 Aug 2009
Something I'm working on...

This will probably be the last of these posts before I make the project public. It has two code parts. The first is model:

import random
            
class Cell(object):
    def __init__(self, i, j, has_bomb):
        self.i, self.j = i, j
        self.has_bomb = has_bomb
class Board(list):
    def __init__(self, size, chance=.2): 
        self.size = size
        self[:] = [[Cell(i, j, random.random() < chance)
            for i in range(size)] for j in range(size)]
    def count(self, cell):
        '''Count the number of bombs near the cell.'''
        return sum(self[j][i].has_bomb
            for i in range(max(0, cell.i-1), min(self.size, cell.i+2))
                for j in range(max(0, cell.j-1), min(self.size, cell.j+2)))
    
board = Board(20)

And the second is the withgui code:

with gui.canvas(width=320, height=320) as canvas:
    for column in board:
        for cell in column:
            @canvas.image('cover.png', x=cell.i*16, y=cell.j*16)
            def on_mouse(image, mouse, cell=cell):
                count = board.count(cell)
                if cell.has_bomb:
                    image.value = 'bomb.png'
                    print 'GAME OVER!'
                elif count:
                    image.destroy()
                    canvas.label(str(count), x=cell.i*16+8, y=cell.j*16+8, 
                        anchor=center)
                else:
                    image.destroy()

Resulting in (youtube vid, sorry about the quality I'm new to this):


Tue, 18 Aug 2009
Something I'm working on...

with kytten...

Mon, 17 Aug 2009
More pausing

I got distracted mucking about with Python 3.1.1 and Tcl/Tk 8.5 today -- withgui works with both (only had to make very minor edits to get it working under Python 3000.)

More interesting update tomorrow, I hope :)

category: Python | permanent link
Fri, 14 Aug 2009
Brief pause

Today was a day of rest so naturally I spent it refactoring the heck out of withgui*.

The upshot is that a couple of artificial restrictions have been removed and the Tkinter reference implementation is now significantly clearer.

* still a working title; no final title yet ("gui", "show", "pomp" and "fjord" are candidates)

Thu, 13 Aug 2009
Something I'm working on...
with gui.canvas(background='antigravity.png') as canvas:
    with canvas.image('smiley.png', x=0, y=20):
        def animate(image):
            while image.x < 200:
                dt = yield
                image.x += 20*dt
        def on_mouse(image, mouse):
            print 'Got me!'

Screen capture at 10 frames per second; original animation runs smoother.

Comic fragment taken from XKCD #353 "Python". Smiley is all original art ;)

Wed, 12 Aug 2009
Something I'm working on...

No update yesterday because I was at MPUG giving a presentation about this stuff...

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)