<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-9266717</id><updated>2011-12-11T16:47:55.733-04:00</updated><category term='i18n'/><category term='crunchy'/><category term='ghop'/><category term='rur-ple'/><category term='pycon'/><title type='text'>Only Python</title><subtitle type='html'>This blog deals almost exclusively with my Python coding activities (rur-ple  and crunchy in particular).</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default?start-index=101&amp;max-results=100'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>154</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-9266717.post-3317604147370670858</id><published>2011-12-09T22:30:00.001-04:00</published><updated>2011-12-09T22:46:26.600-04:00</updated><title type='text'>Porting to Python 3: What are you waiting for?</title><content type='html'>Lately, there seems to be a lot of posts trying to encourage people to port their apps/libraries/modules to Python 3. &amp;nbsp;I'd like to add my voice and perhaps, use as a reminder &lt;a href="http://aroberge.blogspot.com/2009/08/crunchy-10-released.html"&gt;this old post&lt;/a&gt;: more than 2 years ago, Crunchy was made compatible with Python 2.4, 2.5, 2.6 ... and 3.1. &amp;nbsp;At the time, we made the decision to &lt;b&gt;not&lt;/b&gt; follow what seem to be the official recommendation, namely to have one code base based on 2.x and use the 2to3 tool to do the automatic translation. &amp;nbsp;Instead, we decided to have a single code base, which seems the way people are doing it these days. &amp;nbsp;There were of course some challenges, especially as we kept compatibility all the way back to Python 2.4: since Crunchy puts a Python interpreter inside your browser and manipulates what's there, regardless of the encoding, it has to be able to do a lot of string (and bytes for 3.x) manipulations, as well as dealing with incompatible syntax for handling exceptions, etc. &amp;nbsp;when running the code that is fed to it. &amp;nbsp;This was done when there was very little known about best practices for porting from 2.x to 3. &amp;nbsp;Partly as a result, Crunchy's code is not the best example to look at when it comes to doing such a port ... but it certainly demonstrated that it was possible to port a non-trivial program with a tiny team. &amp;nbsp;Now, the situation is quite different, and many more projects have been ported, and you can benefit from their experience. &lt;br /&gt;&lt;br /&gt;So, what are you waiting for?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-3317604147370670858?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/3317604147370670858/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=3317604147370670858' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/3317604147370670858'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/3317604147370670858'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2011/12/porting-to-python-3-what-are-you.html' title='Porting to Python 3: What are you waiting for?'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-5162663907375703962</id><published>2011-09-22T00:38:00.000-03:00</published><updated>2011-09-22T00:39:04.602-03:00</updated><title type='text'>Errors should never pass silently</title><content type='html'>&amp;nbsp;- &lt;i&gt;The Zen of Python&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;br /&gt;&lt;/i&gt;&lt;br /&gt;Lately, I have been programming a lot in Javascript, and have come to appreciate even more what Tim Peters expressed so well in writing what is now known as The Zen of Python. &amp;nbsp;In particular, I appreciate the philosophy that "Errors should never pass silently."&lt;br /&gt;&lt;br /&gt;Those that don't care about Javascript can stop reading now. &amp;nbsp;Others may benefit, and perhaps even enlighten me even further, about a "feature" I came across.&lt;br /&gt;&lt;br /&gt;I'm working on a new website which will include only interactive tutorials. &amp;nbsp;[No, not Crunchy-based ones, but honest-to-goodness interactive tutorials that require nothing but a browser.] &amp;nbsp; To make my life easier, I use &lt;a href="http://jquery.com/"&gt;jQuery&lt;/a&gt; as well as &lt;a href="http://codemirror.net/"&gt;CodeMirror&lt;/a&gt;. &amp;nbsp;A user can edit the code in the CodeMirror editor and view the result "live" in a separate window (html iframe). &amp;nbsp;Every time the content of the editor is changed, the iframe gets updated, as illustrated &lt;a href="http://codemirror.net/demo/preview.html"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I started by using the standard $ jQuery notation inside the html iframe without including a link to jQuery in the iframe, since I already included it in the parent page, and got an error in the console to the effect that $ was not defined. &amp;nbsp;Fair enough. &amp;nbsp;I changed the source to also include a link to jQuery inside the content of the editor (to be copied in the iframe) and the error vanished. &amp;nbsp;However, no javascript code was run from the iframe, not even a simple test alert. &amp;nbsp;Meanwhile, the javascript console remained silent.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;Errors should never pass silently.&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;Eventually, by searching on the web, I was able to find a solution. &amp;nbsp;However, before I found a solution, I encountered a truly puzzling "feature". &lt;br /&gt;&lt;br /&gt;After including a link to jQuery in the editor (which then was copied into the iframe) and reloading the page, if I then proceeded to remove that link from the editor, suddenly all javascript code embedded in the iframe, including all jQuery calls, would work.&lt;br /&gt;&lt;br /&gt;I can just imagine giving these instructions to beginners:&lt;br /&gt;&lt;blockquote&gt;&lt;i&gt;We are going to use jQuery, as you can see from the code in the editor. &amp;nbsp;Now, to begin, you have to select the line where jQuery is loaded and delete it so that the browser (iframe) can use it.&amp;nbsp;&lt;/i&gt;&lt;/blockquote&gt;I still have not figured out how that phantom jQuery worked... and strongly believe that &lt;b&gt;some&lt;/b&gt; error message or warning should have appeared in the javascript console (for Chrome, or Firebug for Firefox).&lt;br /&gt;&lt;br /&gt;In any event, to solve my problem, I had to do 3 things:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Only load jQuery once, in the main window.&lt;/li&gt;&lt;li&gt;Write &lt;b&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;$ = parent.$;&lt;/span&gt;&lt;/b&gt;&amp;nbsp;inside the iframe before using the $ notation. &amp;nbsp;&lt;/li&gt;&lt;li&gt;As I wanted to draw things on a canvas, replace the usual &lt;b&gt;$("#canvas")&lt;/b&gt; jQuery idiom by&amp;nbsp;&lt;b&gt;$("#canvas", document)&lt;/b&gt; everywhere.&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;Now everything appears to work just as expected.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-5162663907375703962?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/5162663907375703962/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=5162663907375703962' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/5162663907375703962'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/5162663907375703962'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2011/09/errors-should-never-pass-silently.html' title='Errors should never pass silently'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-9143656183656474586</id><published>2011-01-16T21:00:00.001-04:00</published><updated>2011-01-16T21:30:34.351-04:00</updated><title type='text'>Book review: Pro Python System Administration</title><content type='html'>Summary: &lt;a href="http://apress.com/book/view/9781430226055"&gt;Pro Python System Administration&lt;/a&gt; is a comprehensive book showing how Python can be used effectively to perform a variety of system administration tasks.&amp;nbsp; I would recommend it highly to anyone having to do system administration work.&amp;nbsp; For more information, please consult the &lt;a href="http://www.sysadminpy.com/"&gt;author's web site&lt;/a&gt;.&lt;br /&gt;===&lt;br /&gt;&lt;br /&gt;There is a saying that "no good deed goes unpunished".&amp;nbsp; I feel that a counterpart should be "no bad talk goes unrewarded".&amp;nbsp; At Pycon 2009, I gave a talk on plugins that has to be amongst the worst presentations I ever gave. Yet, as an unexpected result from that talk, I received a free copy of &lt;a href="http://apress.com/book/view/9781430226055"&gt;Pro Python System Administration&lt;/a&gt; written by Rytis Sileika. This blog entry is a review of that book.&lt;br /&gt;&lt;br /&gt;This book is written for system administrators, something in which I have no experience; therefore, this review will definitely not have the depth that an expert may have given it. &lt;br /&gt;&lt;br /&gt;Four general areas of system administrations are covered: network management, web server and web application management, database system management, and system monitoring.&amp;nbsp; Examples are given on a Linux system with no indication as to whether or not a given example is applicable to other operating systems. Given that Python works with all major operating systems, and that the book focuses on using Python packages, I suspect that the content would be easily adaptable to other environments.&amp;nbsp; &lt;br /&gt;&lt;br /&gt;While the book is classified, on the cover, as being addressed to advanced Python programmers, the author in the book introduction indirectly suggests that this book would be appropriate for people that have some minimal experience with Python.&amp;nbsp; I suspect that the classification on the book cover was done (wrongly) by the editor as I found the examples very readable and I would not claim to be an advanced Python programmer.&lt;br /&gt;&lt;br /&gt;The book is divided into 13 chapters, each focused on one or a few well-defined tasks. While the tasks in a given chapter are relatively independent of those of other chapters, there is a natural progression in terms of topics introduced and it is probably better to read the book in the natural sequence rather than reading chapters randomly - in other words, this book is not simply a random collection of recipes for a series of tasks.&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1. Reading and Collecting Performance Data Using SNMP&lt;br /&gt;&lt;br /&gt;In this chapter, Sileika introduces the Simple Network Management Protocol, or SNMP after which he shows how one can query SNMP devices using Python and the PySNMP library. In the second part of that chapter, he introduces RRDTool, an application for graphing monitoring data, and shows how to interact with it using the rrdtool module. In the last section of this first chapter, he shows how to create web pages (with the eventual goal of displaying on the web monitoring data) using the Jinja2 templating system.&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 2. Managing Devices Using the SOAP API&amp;nbsp; &lt;br /&gt;&lt;br /&gt;In this chapter, Sileika introduces the Simple Object Access Protocol or SOAP and gives examples based on using the Zolera SOAP Infrastructure (ZSI) package.&amp;nbsp; The bulk of the chapter focuses on explaining how to manage and monitor Citrix Netscaler load balancers. The Python logging module is introduced and used.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3. Creating a Web Application for IP Address Accountancy&amp;nbsp;&amp;nbsp; &lt;br /&gt;&lt;br /&gt;In this chapter, the Django framework is introduced and used to build a web application that maintains IP addresses allocation on an internal network.&amp;nbsp; Rather than using the web server included with Django, Sileika shows how to use Django with the Apache web server. &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 4. Integrating the IP Address Application with DHCP &lt;br /&gt;&lt;br /&gt;This chapter is a continuation of the previous one, where the application previously developed is enhanced with the addition of Dynamic Host Configuration Protocol (DHCP) services as well as a few others. More advanced Django methods are included as well as some AJAX calls.&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 5. Maintaining a List of Virtual Hosts in an Apache Configuration File&lt;br /&gt;&lt;br /&gt;Another Django based application is introduced, this time with a focus on the administration side.&lt;br /&gt;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 6. Gathering and Presenting Statistical Data from Apache Log Files&lt;br /&gt;&lt;br /&gt;This chapter focuses on building a plugin-based modular framework to analyze log files.&amp;nbsp; The content of this chapter is the reason why I received a free copy of the book in the first place: Sileika mentioned to me in an email that the architecture described was mostly inspired by the presentation I gave at PyCon with a few modifications that allow for information exchange between the plug-in modules.&amp;nbsp; When I got the original email, I was really surprised given that I had tried to forget about the talk I had given on plugins.&amp;nbsp; &lt;br /&gt;&lt;br /&gt;In my opinion, Sileika does an excellent job in explaining how plugins can be easily created with Python and used effectively to design modular applications.&lt;br /&gt;&lt;br /&gt;Dynamic discovery and loading of plugins is illustrated, and the GeoIP Python library is used to find the physical location corresponding to a given IP address.&lt;br /&gt;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 7. Performing Complex Searches and Reporting on Application Log Files &lt;br /&gt;&lt;br /&gt;This chapter shows how to use Exctractor, an open source log file parser tool, to parse more complex log files than those generated by an Apache server as illustrated in the previous chapter. Of note is the introduction and usage of Python generators as well as an introduction to parsing XML files with Python.&lt;br /&gt;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 8. A Web Site Availability Check Script for Nagios&amp;nbsp; &lt;br /&gt;&lt;br /&gt;This chapter shows how to use Python with Nagios, a network monitoring system. Python packages/modules illustrated include BeautifulSoup, urllib and urllib2.&amp;nbsp; The monitoring scripts developed do more than simply checking for site availability and actually test the web application logic.&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 9. Management and Monitoring Subsystem&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 10. Remote Monitoring Agents &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 11. Statistics Gathering and Reporting&amp;nbsp;&amp;nbsp; &lt;br /&gt;&lt;br /&gt;Chapters 9, 10 and 11 explain how to build a "simple" distributed monitoring system.&amp;nbsp; A number of Python module &amp;amp; packages are used including xmlrpclib, SimpleXMLRPCServer, CherryPy, sqlite3, multiprocessing, ConfigParser, subprocess, Numpy, matplotlib and others.&amp;nbsp; The application developed is a good example of using Python as a "glue language", to use third-party modules &amp;amp; packages.&amp;nbsp; One weakness is that the introduction to statistics included is rather elementary and, I believe, could have been shortened considerably given the intended audience.&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 12. Automatic MySQL Database Performance Tuning&amp;nbsp; &lt;br /&gt;&lt;br /&gt;This chapter revisits the use of plugins, with a slightly more advanced application, where information can be exchanged between the different plugins.&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 13. Using Amazon EC2/S3 as a Data Warehouse Solution&lt;br /&gt;&lt;br /&gt;This last chapter gives a crash course on Amazon's "cloud computing" offerings.&amp;nbsp; It is a good final note to the book, and a good starting point for future explorations.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Overall, I found the book quite readable even though it is outside of my area of expertise.&amp;nbsp; Occasionally, I had the feeling that there were a few "fillers" (e.g. overlong log listings, etc.) that could have been shortened without losing anything of real value.&amp;nbsp; This is very much an applied book, with real life examples that could be either used "as-is" or used as starting points for more extended applications.&lt;br /&gt;&lt;br /&gt;I would recommend this book highly to anyone who has to perform any one of the system administration tasks mentioned above. It is also a good source of non-trivial examples demonstrating the use of a number of Python modules and packages.&amp;nbsp; The code that is given would likely save many hours of development.&amp;nbsp;&amp;nbsp; As is becoming the norm, the source code included in the book is available from the publisher.&amp;nbsp; Also, many of the prototypes covered in the book are available as open source projects at the author's web site http://www.sysadminpy.com, where a discussion forum is also available.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-9143656183656474586?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/9143656183656474586/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=9143656183656474586' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/9143656183656474586'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/9143656183656474586'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2011/01/book-review-pro-python-system.html' title='Book review: Pro Python System Administration'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-3162662710216229986</id><published>2010-08-01T13:01:00.000-03:00</published><updated>2010-08-01T13:01:53.375-03:00</updated><title type='text'>My favourite Python one-liner</title><content type='html'>I am slowly working on two new projects involving web-based interactive Python tutorials and had to set up a local web server for handling ajax request.&amp;nbsp; After browsing the web, I stumbled upon the following one-liner in the &lt;a href="http://docs.python.org/library/simplehttpserver.html"&gt;Python documentation&lt;/a&gt; that I had completely forgotten about:&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;python -m SimpleHTTPServer 8000&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Then, simply pointing the browser at "http://localhost:8000" and all is set to go.&amp;nbsp; Granted, it is not what one might consider a traditional "one-liner" but it works for me.&amp;nbsp; Python's batteries included is certainly a nice feature!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-3162662710216229986?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/3162662710216229986/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=3162662710216229986' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/3162662710216229986'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/3162662710216229986'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2010/08/my-favourite-python-one-liner.html' title='My favourite Python one-liner'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-5352438622863189360</id><published>2010-01-17T16:14:00.000-04:00</published><updated>2010-01-17T16:14:22.228-04:00</updated><title type='text'>Profiling adventures and cython - Faster drawing and conclusion</title><content type='html'>In the last blog post, I made use of cython to speed up some calculations    involving the iterative equation defining the Mandelbrot set.  From    the initial profiling run with 1000 iterations in the second post    to the last run in the previous post, the profiling time went from    575 seconds down to 21 seconds, which is a reduction by a factor of    27.  This is nothing to sneer at.  Yet, I can do better.&lt;br /&gt;Let's start by having a sample profile run with 1000 iterations with the    program as it was last time, but keeping track of more function calls    in the profile.&lt;br /&gt;&lt;pre&gt;      5441165 function calls in 20.484 CPU seconds&lt;br /&gt;&lt;br /&gt;ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;494604    8.461    0.000   14.218    0.000 Tkinter.py:2135(_create)&lt;br /&gt;    11    3.839    0.349   20.315    1.847 mandel2g_cy.pyx:27(create_fractal)&lt;br /&gt;494632    2.053    0.000    5.309    0.000 Tkinter.py:1046(_options)&lt;br /&gt;494657    1.809    0.000    2.811    0.000 Tkinter.py:77(_cnfmerge)&lt;br /&gt;494593    1.522    0.000   16.475    0.000 mandel2g_cy.pyx:21(draw_pixel)&lt;br /&gt;989240    0.752    0.000    0.752    0.000 {method 'update' of 'dict' objects}&lt;br /&gt;494593    0.736    0.000   14.953    0.000 Tkinter.py:2155(create_line)&lt;br /&gt;989257    0.698    0.000    0.698    0.000 {_tkinter._flatten}&lt;br /&gt;494632    0.315    0.000    0.315    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;     1    0.158    0.158    0.158    0.158 {_tkinter.create}&lt;br /&gt;494641    0.131    0.000    0.131    0.000 {callable}&lt;br /&gt;    37    0.009    0.000    0.009    0.000 {built-in method call}&lt;br /&gt;    11    0.001    0.000   20.317    1.847 viewer2b.py:20(draw_fractal)&lt;br /&gt;    12    0.000    0.000    0.000    0.000 viewer.py:60(info)&lt;br /&gt;&lt;/pre&gt;I notice that there are &lt;b&gt;many&lt;/b&gt; function calls: over 5 millions of them.    While most of them appear to take very little time, they do add up    in the end.  It is time to adopt a smarter drawing strategy.&lt;br /&gt;Currently, a "line" is potentially drawn for each pixel.  If I look at    a given fractal drawing, I can see that it could be drawn using    "longer lines", when    consecutive pixels are to be drawn with the same colour.  I can easily    implement this as follows.&lt;br /&gt;&lt;pre class="py" name="code"&gt;def create_fractal(int canvas_width, int canvas_height,&lt;br /&gt;                       double min_x, double min_y, double pixel_size,&lt;br /&gt;                       int nb_iterations, canvas):&lt;br /&gt;    cdef int x, y, start_y, end_y&lt;br /&gt;    cdef double real, imag&lt;br /&gt;    cdef bint start_line&lt;br /&gt;&lt;br /&gt;    for x in range(0, canvas_width):&lt;br /&gt;        real = min_x + x*pixel_size&lt;br /&gt;        start_line = False&lt;br /&gt;        for y in range(0, canvas_height):&lt;br /&gt;            imag = min_y + y*pixel_size&lt;br /&gt;            if mandel(real, imag, nb_iterations):&lt;br /&gt;                if not start_line:&lt;br /&gt;                    start_line = True&lt;br /&gt;                    start_y = canvas_height - y&lt;br /&gt;            else:&lt;br /&gt;                if start_line:&lt;br /&gt;                    start_line = False&lt;br /&gt;                    end_y = canvas_height - y&lt;br /&gt;                    canvas.create_line(x, start_y, x, end_y, fill="black")&lt;br /&gt;        if start_line:&lt;br /&gt;            end_y = canvas_height - y&lt;br /&gt;            canvas.create_line(x, start_y, x, end_y, fill="black")&lt;br /&gt;&lt;/pre&gt;Note that I no longer need the function &lt;code&gt;draw_pixel()&lt;/code&gt;.    The result is a reduction from 20 seconds down to 4 seconds:&lt;br /&gt;&lt;pre&gt;      79092 function calls in 3.959 CPU seconds&lt;br /&gt;&lt;br /&gt;ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;    11    3.552    0.323    3.815    0.347 mandel2h_cy.pyx:21(create_fractal)&lt;br /&gt;  7856    0.150    0.000    0.250    0.000 Tkinter.py:2135(_create)&lt;br /&gt;     1    0.131    0.131    0.131    0.131 {_tkinter.create}&lt;br /&gt;  7884    0.037    0.000    0.091    0.000 Tkinter.py:1046(_options)&lt;br /&gt;  7909    0.030    0.000    0.047    0.000 Tkinter.py:77(_cnfmerge)&lt;br /&gt;  7845    0.014    0.000    0.263    0.000 Tkinter.py:2155(create_line)&lt;br /&gt; 15761    0.014    0.000    0.014    0.000 {_tkinter._flatten}&lt;br /&gt; 15744    0.013    0.000    0.013    0.000 {method 'update' of 'dict' objects}&lt;br /&gt;    37    0.009    0.000    0.009    0.000 {built-in method call}&lt;br /&gt;  7884    0.005    0.000    0.005    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;  7893    0.002    0.000    0.002    0.000 {callable}&lt;br /&gt;    11    0.001    0.000    3.818    0.347 viewer2b.py:20(draw_fractal)&lt;br /&gt;    12    0.000    0.000    0.000    0.000 viewer.py:60(info)&lt;br /&gt;    22    0.000    0.000    0.001    0.000 Tkinter.py:1172(_configure)&lt;br /&gt;&lt;/pre&gt;And it is now again my own code in &lt;code&gt;create_fractal()&lt;/code&gt; that appears    to be the limiting factor.  Thinking back of when I increased the number    of iterations from 100 to 1000, thus only affecting the execution time    of &lt;code&gt;mandel()&lt;/code&gt;, it seemed like this might be a good place    to look at for possible time improvements.  Let's recall what the code    looks like.&lt;br /&gt;&lt;pre class="py" name="code"&gt;cdef inline bint mandel(double real, double imag, int max_iterations=20):&lt;br /&gt;    '''determines if a point is in the Mandelbrot set based on deciding if,&lt;br /&gt;       after a maximum allowed number of iterations, the absolute value of&lt;br /&gt;       the resulting number is greater or equal to 2.'''&lt;br /&gt;    cdef double z_real = 0., z_imag = 0.&lt;br /&gt;    cdef int i&lt;br /&gt;&lt;br /&gt;    for i in range(0, max_iterations):&lt;br /&gt;        z_real, z_imag = ( z_real*z_real - z_imag*z_imag + real,&lt;br /&gt;                           2*z_real*z_imag + imag )&lt;br /&gt;        if (z_real*z_real + z_imag*z_imag) &amp;gt;= 4:&lt;br /&gt;            return False&lt;br /&gt;    return (z_real*z_real + z_imag*z_imag) &amp;lt; 4&lt;br /&gt;&lt;/pre&gt;I used a Pythonic tuple assignement to avoid the use of temporary    variables.  However, in a typical iteration, there will be 4 multiplications    for the tuple re-assigment and two more for the "if" statement, for a total    of 6.  It is certainly possible to reduce the number of multiplications    by using temporary variables, as follows:&lt;br /&gt;&lt;pre class="py" name="code"&gt;cdef inline bint mandel(double real, double imag, int max_iterations=20):&lt;br /&gt;    '''determines if a point is in the Mandelbrot set based on deciding if,&lt;br /&gt;       after a maximum allowed number of iterations, the absolute value of&lt;br /&gt;       the resulting number is greater or equal to 2.'''&lt;br /&gt;    cdef double z_real = 0., z_imag = 0.&lt;br /&gt;    cdef int i&lt;br /&gt;    cdef double zr_sq, zi_sq, z_cross&lt;br /&gt;&lt;br /&gt;    for i in range(0, max_iterations):&lt;br /&gt;        zr_sq = z_real*z_real&lt;br /&gt;        zi_sq = z_imag*z_imag&lt;br /&gt;        z_cross = 2*z_real*z_imag&lt;br /&gt;&lt;br /&gt;        z_real = zr_sq - zi_sq + real&lt;br /&gt;        z_imag = z_cross + imag&lt;br /&gt;        if (zr_sq + zi_sq) &amp;gt;= 4:&lt;br /&gt;            return False&lt;br /&gt;    return (zr_sq + zi_sq) &amp;lt; 4&lt;br /&gt;&lt;/pre&gt;So, there are now fewer multiplications to compute.  Surely, this will    speed up the code:&lt;br /&gt;&lt;pre&gt;      78982 function calls in 4.888 CPU seconds&lt;br /&gt;&lt;br /&gt;ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;    11    4.478    0.407    4.748    0.432 mandel2i_cy.pyx:26(create_fractal)&lt;br /&gt;  7845    0.153    0.000    0.256    0.000 Tkinter.py:2135(_create)&lt;br /&gt;     1    0.128    0.128    0.128    0.128 {_tkinter.create}&lt;br /&gt;  7873    0.040    0.000    0.095    0.000 Tkinter.py:1046(_options)&lt;br /&gt;  7898    0.031    0.000    0.048    0.000 Tkinter.py:77(_cnfmerge)&lt;br /&gt;  7834    0.014    0.000    0.270    0.000 Tkinter.py:2155(create_line)&lt;br /&gt; 15739    0.013    0.000    0.013    0.000 {_tkinter._flatten}&lt;br /&gt; 15722    0.013    0.000    0.013    0.000 {method 'update' of 'dict' objects}&lt;br /&gt;    37    0.009    0.000    0.009    0.000 {built-in method call}&lt;br /&gt;  7873    0.005    0.000    0.005    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;  7882    0.002    0.000    0.002    0.000 {callable}&lt;br /&gt;    11    0.001    0.000    4.750    0.432 viewer2b.py:20(draw_fractal)&lt;br /&gt;    12    0.000    0.000    0.000    0.000 viewer.py:60(info)&lt;br /&gt;     4    0.000    0.000    0.000    0.000 {posix.stat}&lt;br /&gt;&lt;/pre&gt;Alas, that is not the case, as the previous profiling run was slightly    below 4 seconds. [Note that I did run each profiling test at least three    times to prevent any anomalous result.] Apparently my intuition is not a very good    guide when it comes to predicting how cython will be able to optimize    a given function.&lt;br /&gt;So far, the pictures the program has been able to produce have only    been in black and white.  It is time to spruce things up and add colour.    To do this, we will need to make three general changes:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;        We will modify &lt;code&gt;mandel()&lt;/code&gt; so that it returns the number        of iterations required to evaluate that a given point does &lt;b&gt;not&lt;/b&gt;        belong to the set; if it does belong, we will return -1.    &lt;/li&gt;&lt;li&gt;        We will create a colour palette as a Python list.  For a given number        of iterations required by &lt;code&gt;mandel()&lt;/code&gt;, we will pick        a given colour, cycling through the colours from the palette.    &lt;/li&gt;&lt;li&gt;        We will need to change our line drawing method so that we keep track        of the colour (number of iteration) rather than simply whether or        not the point is in the set ("black") or not.    &lt;/li&gt;&lt;/ol&gt;The code is a bit trickier to set up than the previous version, but    it uses a similar logic.  To determine what colour to draw, we first    calculate the colour of the first pixel, and then loop through all    the others along a same line.  Each time we see a colour change, it signals    that we need to draw a line up to that point in the colour used up to that    point ("current_colour") and assign the new colour as the new "current" one.    When we reach the end of a line (column in the code...), we need to ensure    that we draw the last line segment.  Without further ado, here is the    complete code of the revised cython module.&lt;br /&gt;&lt;pre class="py" name="code"&gt;# mandel3cy.pyx&lt;br /&gt;# cython: profile=True&lt;br /&gt;&lt;br /&gt;import cython&lt;br /&gt;&lt;br /&gt;def make_palette():&lt;br /&gt;    '''sample coloring scheme for the fractal - feel free to experiment'''&lt;br /&gt;    colours = []&lt;br /&gt;&lt;br /&gt;    for i in range(0, 25):&lt;br /&gt;        colours.append('#%02x%02x%02x' % (i*10, i*8, 50 + i*8))&lt;br /&gt;    for i in range(25, 5, -1):&lt;br /&gt;        colours.append('#%02x%02x%02x' % (50 + i*8, 150+i*2,  i*10))&lt;br /&gt;    for i in range(10, 2, -1):&lt;br /&gt;        colours.append('#00%02x30' % (i*15))&lt;br /&gt;    return colours&lt;br /&gt;&lt;br /&gt;colours = make_palette()&lt;br /&gt;cdef int nb_colours = len(colours)&lt;br /&gt;&lt;br /&gt;@cython.profile(False)&lt;br /&gt;cdef inline int mandel(double real, double imag, int max_iterations=20):&lt;br /&gt;    '''determines if a point is in the Mandelbrot set based on deciding if,&lt;br /&gt;       after a maximum allowed number of iterations, the absolute value of&lt;br /&gt;       the resulting number is greater or equal to 2.'''&lt;br /&gt;    cdef double z_real = 0., z_imag = 0.&lt;br /&gt;    cdef int i&lt;br /&gt;&lt;br /&gt;    for i in range(0, max_iterations):&lt;br /&gt;        z_real, z_imag = ( z_real*z_real - z_imag*z_imag + real,&lt;br /&gt;                           2*z_real*z_imag + imag )&lt;br /&gt;        if (z_real*z_real + z_imag*z_imag) &amp;gt;= 4:&lt;br /&gt;            return i&lt;br /&gt;    return -1&lt;br /&gt;&lt;br /&gt;def create_fractal(int canvas_width, int canvas_height,&lt;br /&gt;                       double min_x, double min_y, double pixel_size,&lt;br /&gt;                       int nb_iterations, canvas):&lt;br /&gt;    global colours, nb_colours&lt;br /&gt;    cdef int x, y, start_y, end_y, current_colour, new_colour&lt;br /&gt;    cdef double real, imag&lt;br /&gt;&lt;br /&gt;    for x in range(0, canvas_width):&lt;br /&gt;        real = min_x + x*pixel_size&lt;br /&gt;        start_y = canvas_height&lt;br /&gt;        current_colour = mandel(real, min_y, nb_iterations)&lt;br /&gt;        for y in range(1, canvas_height):&lt;br /&gt;            imag = min_y + y*pixel_size&lt;br /&gt;            new_colour = mandel(real, imag, nb_iterations)&lt;br /&gt;&lt;br /&gt;            if new_colour != current_colour:&lt;br /&gt;                if current_colour == -1:&lt;br /&gt;                    canvas.create_line(x, start_y, x, canvas_height-y,&lt;br /&gt;                                        fill="black")&lt;br /&gt;                else:&lt;br /&gt;                    canvas.create_line(x, start_y, x, canvas_height-y,&lt;br /&gt;                                        fill=colours[current_colour%nb_colours])&lt;br /&gt;                current_colour = new_colour&lt;br /&gt;                start_y = canvas_height - y&lt;br /&gt;&lt;br /&gt;        if current_colour == -1:&lt;br /&gt;            canvas.create_line(x, start_y, x, 0, fill="black")&lt;br /&gt;        else:&lt;br /&gt;            canvas.create_line(x, start_y, x, 0,&lt;br /&gt;                                        fill=colours[current_colour%nb_colours])&lt;br /&gt;&lt;/pre&gt;If we profile this code, we find out that it takes about three times as    long to generate a colour picture than it did to generate a black    and white one - at least, for the starting configuration...&lt;br /&gt;&lt;pre&gt;      2370682 function calls in 12.638 CPU seconds&lt;br /&gt;&lt;br /&gt;ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;    11    4.682    0.426   12.184    1.108 mandel3_cy.pyx:36(create_fractal)&lt;br /&gt;237015    4.310    0.000    7.084    0.000 Tkinter.py:2135(_create)&lt;br /&gt;237043    1.019    0.000    2.575    0.000 Tkinter.py:1046(_options)&lt;br /&gt;237068    0.877    0.000    1.353    0.000 Tkinter.py:77(_cnfmerge)&lt;br /&gt;     1    0.443    0.443    0.443    0.443 {_tkinter.create}&lt;br /&gt;237004    0.418    0.000    7.502    0.000 Tkinter.py:2155(create_line)&lt;br /&gt;474062    0.361    0.000    0.361    0.000 {method 'update' of 'dict' objects}&lt;br /&gt;474079    0.313    0.000    0.313    0.000 {_tkinter._flatten}&lt;br /&gt;237043    0.143    0.000    0.143    0.000 {method 'items' of 'dict' objects}&lt;br /&gt;237052    0.061    0.000    0.061    0.000 {callable}&lt;br /&gt;    37    0.009    0.000    0.009    0.000 {built-in method call}&lt;br /&gt;    11    0.000    0.000   12.186    1.108 viewer3.py:20(draw_fractal)&lt;br /&gt;    12    0.000    0.000    0.000    0.000 viewer.py:60(info)&lt;br /&gt;     4    0.000    0.000    0.000    0.000 {posix.stat}&lt;br /&gt;&lt;/pre&gt;We also generate some nice pictures! First, using only 100 iterations.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_KpSgL5zKAvE/S1NtAr8m1xI/AAAAAAAAACo/mCMGSyIoHOg/s1600-h/colour100.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/_KpSgL5zKAvE/S1NtAr8m1xI/AAAAAAAAACo/mCMGSyIoHOg/s400/colour100.png" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;Notice how the Mandelbrot set boundary seems rather smooth ... this is clearly a sign that we might be missing something.&amp;nbsp; Increasing the number of iterations to 1000 reveals a different picture.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_KpSgL5zKAvE/S1NteZ71eSI/AAAAAAAAACw/QjTcfNsxKGY/s1600-h/colour1000.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/_KpSgL5zKAvE/S1NteZ71eSI/AAAAAAAAACw/QjTcfNsxKGY/s400/colour1000.png" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;We see much more details and many points that were wrongly identified as being part of the Mandelbrot sets are correctly excluded (and colored!).&amp;nbsp; What happens if we look at a region that contains more points inside the Mandelbrot set (thus requiring more iterations) and increase the number of iterations to 1500 (as I found that 1000 was not enough in this region).&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_KpSgL5zKAvE/S1NuBc42y7I/AAAAAAAAAC4/IHTAi1d8r2Q/s1600-h/colour1500.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/_KpSgL5zKAvE/S1NuBc42y7I/AAAAAAAAAC4/IHTAi1d8r2Q/s400/colour1500.png" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Unfortunately, the profiling and the timing information displayed    does not tell the entire story.  In practice, I found that it would take    many more seconds (sometimes more than 10) for the canvas to be updated    than the timing information given for each of the three pictures displayed above.  Something is happening behind the scenewhen the picture is updated on the screen and which is not being recorded by my simple timing method.&lt;br /&gt;&lt;br /&gt;For comparison, I had a look at    &lt;a href="http://nedbatchelder.com/code/aptus/"&gt;Aptus&lt;/a&gt;, a program    that uses a hand-coded C extension for speed.  Actually, I had tried    Aptus a few years ago, when Ned Batchelder first mentioned it, and    tried it again for fun as I was working on my own version.  Aptus can produce    really nice pictures, really fast.  Here's an example that was produced,    according to the timing given by Aptus itself in 0.25 seconds.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_KpSgL5zKAvE/S1NubA8hTLI/AAAAAAAAADA/ygx-QYA4s3Y/s1600-h/aptus.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/_KpSgL5zKAvE/S1NubA8hTLI/AAAAAAAAADA/ygx-QYA4s3Y/s400/aptus.png" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Note that the timing given by Aptus seems to be truly representative,    unlike the timing for my program.  I should mention that, in addition to using    a hand-crafted C extension, Aptus uses &lt;a href="http://wxpython.org/"&gt;wxPython&lt;/a&gt; instead of Tkinter, and    it also uses &lt;a href="http://www.pythonware.com/products/pil/"&gt;PIL&lt;/a&gt; and &lt;a href="http://numpy.scipy.org/"&gt;numpy&lt;/a&gt;, both of which are known for their speed.  It &lt;i&gt;might&lt;/i&gt;    be possible that using PIL and numpy with my program would improve the    speed significantly.  However, all three libraries are not part of the    standard library and so do not meet the constraints I had decided upon at    the beginning.&lt;br /&gt;&lt;br /&gt;This concludes this profiling experiment ... at least for now.   I should    emphasize that the goal of these posts was to record a profiling experiment    using &lt;a href="http://cython.org/"&gt;cython&lt;/a&gt;.  I do not pretend that this code was especially hand-crafted for    speed ... even though I did try some simple changes to improve its speed.  I may revisit this application at a later time, especially if someone more    experienced can point out ways to increase the speed significantly, preferably    while staying within the constraints I set up at the beginning: other than    cython, use only modules from the standard library.  However, I would    be interested if anyone adapts this code to use PIL and/or numpy in a straightforward    way and, in doing so, increases the speed significantly.&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-5352438622863189360?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/5352438622863189360/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=5352438622863189360' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/5352438622863189360'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/5352438622863189360'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2010/01/profiling-adventures-and-cython-faster.html' title='Profiling adventures and cython - Faster drawing and conclusion'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_KpSgL5zKAvE/S1NtAr8m1xI/AAAAAAAAACo/mCMGSyIoHOg/s72-c/colour100.png' height='72' width='72'/><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-8913951391285780800</id><published>2010-01-17T13:23:00.004-04:00</published><updated>2010-01-17T13:28:48.171-04:00</updated><title type='text'>Profiling adventures and cython - Introducing cython</title><content type='html'>In the previous blog post, I made some attempts at speeding up the    function &lt;code&gt;mandel()&lt;/code&gt; by making changes in the Python code.    While I had some success in doing so, it was clearly not enough    for my purpose.  As a result, I will now try to use cython.  Before    I do this, I note again the result from the last profiling run,    limiting the information to the 5 longest-running functions or methods.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;       3673150 function calls in 84.807 CPU seconds&lt;br /&gt;&lt;br /&gt; ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;3168000   73.210    0.000   73.210    0.000 mandel1c.py:7(mandel)&lt;br /&gt;     11   10.855    0.987   84.204    7.655 viewer1.py:23(draw_fractal)&lt;br /&gt;      1    0.593    0.593    0.593    0.593 {_tkinter.create}&lt;br /&gt; 504530    0.137    0.000    0.137    0.000 viewer1.py:17(draw_pixel)&lt;br /&gt;     37    0.009    0.000    0.009    0.000 {built-in method call}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The goal of cython could be described as providing an easy way to convert    a Python module into a C extension.  This is what I will    do.  [There are other ways to work with cython extensions than    what I use here; for more information, please    consult the &lt;a href="http://cython.org/"&gt;cython web site&lt;/a&gt;.]    &lt;b&gt;Note that I am NOT a cython expert; this is only the first project    for which I use cython.&lt;/b&gt;  While I am not interested in creating an    application for distribution, and hence do not use the setup method    for cython, it is quite possible that there are better    ways to use cython than what I explore here.&lt;br /&gt;I first start by taking my existing module and copying it into a new    file, with a ".pyx" extension instead of the traditional ".py".&lt;br /&gt;&lt;br /&gt;&lt;pre class="py" name="code"&gt;# mandel2cy.pyx&lt;br /&gt;# cython: profile=True&lt;br /&gt;&lt;br /&gt;def mandel(c, max_iterations=20):&lt;br /&gt;    '''determines if a point is in the Mandelbrot set based on deciding if,&lt;br /&gt;       after a maximum allowed number of iterations, the absolute value of&lt;br /&gt;       the resulting number is greater or equal to 2.'''&lt;br /&gt;    z = 0&lt;br /&gt;    for i in range(0, max_iterations):&lt;br /&gt;        z = z**2 + c&lt;br /&gt;        if abs(z) &amp;gt;= 4:&lt;br /&gt;            return False&lt;br /&gt;    return abs(z) &amp;lt; 2&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Note that I have removed the equivalence between    &lt;code&gt;range&lt;/code&gt; and &lt;code&gt;xrange&lt;/code&gt;.  The reason I have done this    is because with &lt;code&gt;xrange&lt;/code&gt; present like this in the file    results in a compilation    error when running cython with Python 3.1.  Furthermore, as will    be seen later, it is not really needed even for Python 2.x when using    cython properly.&lt;br /&gt;I have also included a commented line stating that 'profile' was equal    to True; this is a cython directive that will enable the Python profiler    to also include cython functions in its tally.&lt;br /&gt;&lt;br /&gt;In order to import this module, I also need to modify the viewer to    import the cython module. Here is the new version.&lt;br /&gt;&lt;pre class="py" name="code"&gt;# viewer2.py&lt;br /&gt;&lt;br /&gt;import pyximport&lt;br /&gt;pyximport.install()&lt;br /&gt;&lt;br /&gt;from mandel2_cy import mandel&lt;br /&gt;from viewer import Viewer&lt;br /&gt;import time&lt;br /&gt;&lt;br /&gt;import sys&lt;br /&gt;if sys.version_info &amp;lt; (3,):&lt;br /&gt;    import Tkinter as tk&lt;br /&gt;    range = xrange&lt;br /&gt;else:&lt;br /&gt;    import tkinter as tk&lt;br /&gt;&lt;br /&gt;class FancyViewer(Viewer):&lt;br /&gt;    '''Application to display fractals'''&lt;br /&gt;&lt;br /&gt;    def draw_pixel(self, x, y):&lt;br /&gt;        '''Simulates drawing a given pixel in black by drawing a black line&lt;br /&gt;           of length equal to one pixel.'''&lt;br /&gt;        return&lt;br /&gt;        #self.canvas.create_line(x, y, x+1, y, fill="black")&lt;br /&gt;&lt;br /&gt;    def draw_fractal(self):&lt;br /&gt;        '''draws a fractal on the canvas'''&lt;br /&gt;        self.calculating = True&lt;br /&gt;        begin = time.time()&lt;br /&gt;        # clear the canvas&lt;br /&gt;        self.canvas.create_rectangle(0, 0, self.canvas_width,&lt;br /&gt;                                    self.canvas_height, fill="white")&lt;br /&gt;        for x in range(0, self.canvas_width):&lt;br /&gt;            real = self.min_x + x*self.pixel_size&lt;br /&gt;            for y in range(0, self.canvas_height):&lt;br /&gt;                imag = self.min_y + y*self.pixel_size&lt;br /&gt;                c = complex(real, imag)&lt;br /&gt;                if mandel(c, self.nb_iterations):&lt;br /&gt;                    self.draw_pixel(x, self.canvas_height - y)&lt;br /&gt;        self.status.config(text="Time required = %.2f s  [%s iterations]  %s" %(&lt;br /&gt;                                (time.time() - begin), self.nb_iterations,&lt;br /&gt;                                                                self.zoom_info))&lt;br /&gt;        self.status2.config(text=self.info())&lt;br /&gt;        self.calculating = False&lt;br /&gt;&lt;br /&gt;if __name__ == "__main__":&lt;br /&gt;    root = tk.Tk()&lt;br /&gt;    app = FancyViewer(root)&lt;br /&gt;    root.mainloop()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Other than the top few lines, nothing has changed.  Time to run    the profiler with this new version.&lt;br /&gt;&lt;pre&gt;       6841793 function calls in 50.145 CPU seconds&lt;br /&gt;&lt;br /&gt; ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;3168000   35.913    0.000   35.913    0.000 mandel2_cy.pyx:4(mandel)&lt;br /&gt;     11   10.670    0.970   48.754    4.432 viewer2.py:26(draw_fractal)&lt;br /&gt;3168000    2.001    0.000   37.914    0.000 {mandel2_cy.mandel}&lt;br /&gt;      1    1.356    1.356    1.356    1.356 {_tkinter.create}&lt;br /&gt; 505173    0.167    0.000    0.167    0.000 viewer2.py:20(draw_pixel)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;A reduction from 85 to 50 seconds; cython must be doing something    right!  Note that the calls to &lt;code&gt;abs()&lt;/code&gt; have been eliminated    by using cython.  All I did is import the module via Python without    making any other change to the code.&lt;br /&gt;Note also that &lt;code&gt;mandel&lt;/code&gt; appears twice: once (the longest running) as    the function defined on line 8 of mandel2_cy.pyx, and once as    a object belonging to the module mandel2_cy.  I will come back to this    later but, for now, I will do some changes to help cython do even better.&lt;br /&gt;As mentioned before, cython is a tool to help create C extensions.    One of the differences between C and Python is that variables    have a declared type in C.  If one tells cython about what type a given    variable is, cython can often use that information to make the code run    faster.  As an example, I know that two of the variables are of type    integers which is a native C type; I can    &lt;a href="http://docs.cython.org/src/quickstart/cythonize.html"&gt;add this information&lt;/a&gt;    as follows.&lt;br /&gt;&lt;pre class="py" name="code"&gt;# mandel2a_cy.pyx&lt;br /&gt;# cython: profile=True&lt;br /&gt;&lt;br /&gt;def mandel(c, int max_iterations=20):&lt;br /&gt;    '''determines if a point is in the Mandelbrot set based on deciding if,&lt;br /&gt;       after a maximum allowed number of iterations, the absolute value of&lt;br /&gt;       the resulting number is greater or equal to 2.'''&lt;br /&gt;    cdef int i&lt;br /&gt;    z = 0&lt;br /&gt;    for i in range(0, max_iterations):&lt;br /&gt;        z = z**2 + c&lt;br /&gt;        if abs(z) &amp;gt;= 2:&lt;br /&gt;            return False&lt;br /&gt;    return abs(z) &amp;lt; 2&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Running the profiler with this change yields the following:&lt;br /&gt;&lt;pre&gt;       6841793 function calls in 39.860 CPU seconds&lt;br /&gt;&lt;br /&gt; ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;3168000   27.431    0.000   27.431    0.000 mandel2a_cy.pyx:4(mandel)&lt;br /&gt;     11    9.869    0.897   39.339    3.576 viewer2.py:26(draw_fractal)&lt;br /&gt;3168000    1.906    0.000   29.337    0.000 {mandel2a_cy.mandel}&lt;br /&gt;      1    0.511    0.511    0.511    0.511 {_tkinter.create}&lt;br /&gt; 505173    0.131    0.000    0.131    0.000 viewer2.py:20(draw_pixel)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Another significant time reduction, this time of the order of 20%.    And we didn't tell cython that "z" and "c" are complex yet.&lt;br /&gt;&lt;br /&gt;Actually, C does not have a complex data type.  So, I can choose one    of two strategies:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;        I can change the code so that I deal only with real numbers,        by working myself how to multiply and add complex numbers.    &lt;/li&gt;&lt;li&gt;        I can use some        &lt;a href="http://docs.cython.org/src/userguide/extension_types.html#external-extension-types"&gt;        special cython technique&lt;/a&gt; to extract all the        relevant information about the Python built-in complex data type        without changing the code inside the function (other than        adding some type declaration).    &lt;/li&gt;&lt;/ol&gt;I will choose the second of these methods and see what it gives. The    required changes are as follows:&lt;br /&gt;&lt;pre class="py" name="code"&gt;# mandel2b_cy.pyx&lt;br /&gt;# cython: profile=True&lt;br /&gt;&lt;br /&gt;cdef extern from "complexobject.h":&lt;br /&gt;&lt;br /&gt;    struct Py_complex:&lt;br /&gt;        double real&lt;br /&gt;        double imag&lt;br /&gt;&lt;br /&gt;    ctypedef class __builtin__.complex [object PyComplexObject]:&lt;br /&gt;        cdef Py_complex cval&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;def mandel(complex c, int max_iterations=20):&lt;br /&gt;    '''determines if a point is in the Mandelbrot set based on deciding if,&lt;br /&gt;       after a maximum allowed number of iterations, the absolute value of&lt;br /&gt;       the resulting number is greater or equal to 2.'''&lt;br /&gt;    cdef int i&lt;br /&gt;    cdef complex z&lt;br /&gt;&lt;br /&gt;    z = 0. + 0.j&lt;br /&gt;&lt;br /&gt;    for i in range(0, max_iterations):&lt;br /&gt;        z = z**2 + c&lt;br /&gt;        if abs(z) &amp;gt;= 2:&lt;br /&gt;            return False&lt;br /&gt;    return abs(z) &amp;lt; 2&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The timing results are the following:&lt;br /&gt;&lt;pre&gt;       6841793 function calls in 38.424 CPU seconds&lt;br /&gt;&lt;br /&gt; ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;3168000   26.771    0.000   26.771    0.000 mandel2b_cy.pyx:14(mandel)&lt;br /&gt;     11    9.435    0.858   38.209    3.474 viewer2.py:26(draw_fractal)&lt;br /&gt;3168000    1.865    0.000   28.636    0.000 {mandel2b_cy.mandel}&lt;br /&gt;      1    0.205    0.205    0.205    0.205 {_tkinter.create}&lt;br /&gt; 505173    0.136    0.000    0.136    0.000 viewer2.py:20(draw_pixel)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The time difference between this run and the previous one is within    the variation I observe from one profiling run to the next (using exactly    the same program).  Therefore, I conclude that this latest attempt    didn't speed up the code.  It is possible that I have overlooked something    to ensure that cython could make use of the information about the    complex datatype more efficiently ...    It seems like I need a different strategy. I will resort    to doing the complex algebra myself, and work only with real numbers.    Here's the modified code for the mandel module.&lt;br /&gt;&lt;pre class="py" name="code"&gt;# mandel2c_cy.pyx&lt;br /&gt;# cython: profile=True&lt;br /&gt;&lt;br /&gt;def mandel(double real, double imag, int max_iterations=20):&lt;br /&gt;    '''determines if a point is in the Mandelbrot set based on deciding if,&lt;br /&gt;       after a maximum allowed number of iterations, the absolute value of&lt;br /&gt;       the resulting number is greater or equal to 2.'''&lt;br /&gt;    cdef double z_real = 0., z_imag = 0.&lt;br /&gt;    cdef int i&lt;br /&gt;&lt;br /&gt;    for i in range(0, max_iterations):&lt;br /&gt;        z_real, z_imag = ( z_real*z_real - z_imag*z_imag + real,&lt;br /&gt;                     2*z_real*z_imag + imag )&lt;br /&gt;        if (z_real*z_real + z_imag*z_imag) &amp;gt;= 4:&lt;br /&gt;            return False&lt;br /&gt;    return (z_real*z_real + z_imag*z_imag) &amp;lt; 4&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;I also change the call within &lt;code&gt;draw_fractal()&lt;/code&gt; so that    I don't use complex variables.  The result is extremely encouraging:&lt;br /&gt;&lt;pre&gt;       6841793 function calls in 7.205 CPU seconds&lt;br /&gt;&lt;br /&gt; ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;     11    4.379    0.398    7.066    0.642 viewer2a.py:26(draw_fractal)&lt;br /&gt;3168000    1.557    0.000    2.570    0.000 {mandel2c_cy.mandel}&lt;br /&gt;3168000    1.013    0.000    1.013    0.000 mandel2c_cy.pyx:4(mandel)&lt;br /&gt;      1    0.130    0.130    0.130    0.130 {_tkinter.create}&lt;br /&gt; 505173    0.114    0.000    0.114    0.000 viewer2a.py:20(draw_pixel)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This total execution time has been reduced from 38 to 7 seconds.    &lt;code&gt;mandel()&lt;/code&gt; is no longer the largest contributor to the    overall execution time; &lt;code&gt;draw_fractal()&lt;/code&gt; is.  However,    the program is still a bit too slow: without actually doing any drawing,    it takes approximately 0.6 seconds to generate one fractal image.    However, I can do better.  Looking at the code, I notice that    &lt;code&gt;draw_fractal()&lt;/code&gt; contains two embedded for loops, resulting to    all those calls to    &lt;code&gt;mandel()&lt;/code&gt;.  Remember how telling cython about integer types    used in loops sped up the code?   This suggest that perhaps I should    do something similar and move some    of the code of &lt;code&gt;draw_fractal()&lt;/code&gt; to the cython module.    Here's a modified viewer module.&lt;br /&gt;&lt;pre class="py" name="code"&gt;# viewer2b.py&lt;br /&gt;&lt;br /&gt;import pyximport&lt;br /&gt;pyximport.install()&lt;br /&gt;&lt;br /&gt;from mandel2d_cy import create_fractal&lt;br /&gt;from viewer import Viewer&lt;br /&gt;import time&lt;br /&gt;&lt;br /&gt;import sys&lt;br /&gt;if sys.version_info &amp;lt; (3,):&lt;br /&gt;    import Tkinter as tk&lt;br /&gt;    range = xrange&lt;br /&gt;else:&lt;br /&gt;    import tkinter as tk&lt;br /&gt;&lt;br /&gt;class FancyViewer(Viewer):&lt;br /&gt;    '''Application to display fractals'''&lt;br /&gt;&lt;br /&gt;    def draw_fractal(self):&lt;br /&gt;        '''draws a fractal on the canvas'''&lt;br /&gt;        self.calculating = True&lt;br /&gt;        begin = time.time()&lt;br /&gt;        # clear the canvas&lt;br /&gt;        self.canvas.create_rectangle(0, 0, self.canvas_width,&lt;br /&gt;                                    self.canvas_height, fill="white")&lt;br /&gt;        create_fractal(self.canvas_width, self.canvas_height,&lt;br /&gt;                       self.min_x, self.min_y, self.pixel_size,&lt;br /&gt;                       self.nb_iterations, self.canvas)&lt;br /&gt;        self.status.config(text="Time required = %.2f s  [%s iterations]  %s" %(&lt;br /&gt;                                (time.time() - begin), self.nb_iterations,&lt;br /&gt;                                                                self.zoom_info))&lt;br /&gt;        self.status2.config(text=self.info())&lt;br /&gt;        self.calculating = False&lt;br /&gt;&lt;br /&gt;if __name__ == "__main__":&lt;br /&gt;    root = tk.Tk()&lt;br /&gt;    app = FancyViewer(root)&lt;br /&gt;    root.mainloop()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And here is the new cython module, without any additional type declaration.&lt;br /&gt;&lt;pre class="py" name="code"&gt;# mandel2d_cy.pyx&lt;br /&gt;# cython: profile=True&lt;br /&gt;&lt;br /&gt;def mandel(double real, double imag, int max_iterations=20):&lt;br /&gt;    '''determines if a point is in the Mandelbrot set based on deciding if,&lt;br /&gt;       after a maximum allowed number of iterations, the absolute value of&lt;br /&gt;       the resulting number is greater or equal to 2.'''&lt;br /&gt;    cdef double z_real = 0., z_imag = 0.&lt;br /&gt;    cdef int i&lt;br /&gt;&lt;br /&gt;    for i in range(0, max_iterations):&lt;br /&gt;        z_real, z_imag = ( z_real*z_real - z_imag*z_imag + real,&lt;br /&gt;                     2*z_real*z_imag + imag )&lt;br /&gt;        if (z_real*z_real + z_imag*z_imag) &amp;gt;= 4:&lt;br /&gt;            return False&lt;br /&gt;    return (z_real*z_real + z_imag*z_imag) &amp;lt; 4&lt;br /&gt;&lt;br /&gt;def draw_pixel(x, y, canvas):&lt;br /&gt;    '''Simulates drawing a given pixel in black by drawing a black line&lt;br /&gt;       of length equal to one pixel.'''&lt;br /&gt;    return&lt;br /&gt;    #canvas.create_line(x, y, x+1, y, fill="black")&lt;br /&gt;&lt;br /&gt;def create_fractal(canvas_width, canvas_height,&lt;br /&gt;                       min_x, min_y, pixel_size,&lt;br /&gt;                       nb_iterations, canvas):&lt;br /&gt;    for x in range(0, canvas_width):&lt;br /&gt;        real = min_x + x*pixel_size&lt;br /&gt;        for y in range(0, canvas_height):&lt;br /&gt;            imag = min_y + y*pixel_size&lt;br /&gt;            if mandel(real, imag, nb_iterations):&lt;br /&gt;                draw_pixel(x, canvas_height - y, canvas)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The profiling result is as follows:&lt;br /&gt;&lt;pre&gt;       3673815 function calls in 3.873 CPU seconds&lt;br /&gt;&lt;br /&gt; ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;     11    2.632    0.239    3.706    0.337 mandel2d_cy.pyx:24(create_fractal)&lt;br /&gt;3168000    1.002    0.000    1.002    0.000 mandel2d_cy.pyx:4(mandel)&lt;br /&gt;      1    0.155    0.155    0.155    0.155 {_tkinter.create}&lt;br /&gt; 505173    0.072    0.000    0.072    0.000 mandel2d_cy.pyx:18(draw_pixel)&lt;br /&gt;     37    0.009    0.000    0.009    0.000 {built-in method call}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Simply by moving over some of the code to the cython module, I have reduced    the profiling time to almost half of it previous value.  Looking more    closely at the profiling results, I also notice that calls to    &lt;code&gt;mandel()&lt;/code&gt; now only appear once; some overhead in calling    cython functions from python modules has disappeared. Let's see what happens    if I now add some type information.&lt;br /&gt;&lt;pre class="py" name="code"&gt;def create_fractal(int canvas_width, int canvas_height,&lt;br /&gt;                       double min_x, double min_y, double pixel_size,&lt;br /&gt;                       int nb_iterations, canvas):&lt;br /&gt;    cdef int x, y&lt;br /&gt;    cdef double real, imag&lt;br /&gt;&lt;br /&gt;    for x in range(0, canvas_width):&lt;br /&gt;        real = min_x + x*pixel_size&lt;br /&gt;        for y in range(0, canvas_height):&lt;br /&gt;            imag = min_y + y*pixel_size&lt;br /&gt;            if mandel(real, imag, nb_iterations):&lt;br /&gt;                draw_pixel(x, canvas_height - y, canvas)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The result is only slightly better:&lt;br /&gt;&lt;pre&gt;       3673815 function calls in 3.475 CPU seconds&lt;br /&gt;&lt;br /&gt; ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;     11    2.189    0.199    3.308    0.301 mandel2e_cy.pyx:24(create_fractal)&lt;br /&gt;3168000    1.046    0.000    1.046    0.000 mandel2e_cy.pyx:4(mandel)&lt;br /&gt;      1    0.135    0.135    0.135    0.135 {_tkinter.create}&lt;br /&gt; 505173    0.074    0.000    0.074    0.000 mandel2e_cy.pyx:18(draw_pixel)&lt;br /&gt;     37    0.028    0.001    0.028    0.001 {built-in method call}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;However, one thing I remember from the little I know about C it that,    not only do variables have    to be declared to be of a certain type, but the same has to be done to    functions as well.  Here, &lt;code&gt;mandel()&lt;/code&gt; has not been declared    to be of a specific type, so cython assumes it to be a generic    Python object.  After reading the cython documentation, and noticing    that &lt;code&gt;mandel()&lt;/code&gt; is only called from within the cython    module, I conclude that not only should I specify the type for    &lt;code&gt;mandel()&lt;/code&gt; but that it probably makes sense to    specify that it can be "inlined"; I also    do the same for &lt;code&gt;draw_pixel()&lt;/code&gt;.&lt;br /&gt;&lt;pre class="py" name="code"&gt;# mandel2f_cy.pyx&lt;br /&gt;# cython: profile=True&lt;br /&gt;&lt;br /&gt;cdef inline bint mandel(double real, double imag, int max_iterations=20):&lt;br /&gt;    '''determines if a point is in the Mandelbrot set based on deciding if,&lt;br /&gt;       after a maximum allowed number of iterations, the absolute value of&lt;br /&gt;       the resulting number is greater or equal to 2.'''&lt;br /&gt;    cdef double z_real = 0., z_imag = 0.&lt;br /&gt;    cdef int i&lt;br /&gt;&lt;br /&gt;    for i in range(0, max_iterations):&lt;br /&gt;        z_real, z_imag = ( z_real*z_real - z_imag*z_imag + real,&lt;br /&gt;                     2*z_real*z_imag + imag )&lt;br /&gt;        if (z_real*z_real + z_imag*z_imag) &amp;gt;= 4:&lt;br /&gt;            return False&lt;br /&gt;    return (z_real*z_real + z_imag*z_imag) &amp;lt; 4&lt;br /&gt;&lt;br /&gt;cdef inline void draw_pixel(x, y, canvas):&lt;br /&gt;    '''Simulates drawing a given pixel in black by drawing a black line&lt;br /&gt;       of length equal to one pixel.'''&lt;br /&gt;    return&lt;br /&gt;    #canvas.create_line(x, y, x+1, y, fill="black")&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This yields a nice improvement.&lt;br /&gt;&lt;pre&gt;       3673815 function calls in 2.333 CPU seconds&lt;br /&gt;&lt;br /&gt; ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;     11    1.190    0.108    2.194    0.199 mandel2f_cy.pyx:24(create_fractal)&lt;br /&gt;3168000    0.930    0.000    0.930    0.000 mandel2f_cy.pyx:4(mandel)&lt;br /&gt;      1    0.127    0.127    0.127    0.127 {_tkinter.create}&lt;br /&gt; 505173    0.074    0.000    0.074    0.000 mandel2f_cy.pyx:18(draw_pixel)&lt;br /&gt;     37    0.009    0.000    0.009    0.000 {built-in method call}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;However... I asked cython to "inline" &lt;code&gt;mandel&lt;/code&gt;,    thus treating them as a pure    C function.  Yet, they both appear in the Python profiling information, which    was not the case for &lt;code&gt;abs()&lt;/code&gt; once I used cython for the    first time.  The reason it appears is that cython has been instructed    to profile all functions in the module, via the directive at the top.    I can selectively turn off the profiling for an individual function    by importing the "cython module" and using a special purpose    decorator as follows.&lt;br /&gt;&lt;pre class="py" name="code"&gt;# mandel2g_cy.pyx&lt;br /&gt;# cython: profile=True&lt;br /&gt;&lt;br /&gt;import cython&lt;br /&gt;&lt;br /&gt;@cython.profile(False)&lt;br /&gt;cdef inline bint mandel(double real, double imag, int max_iterations=20):&lt;br /&gt;    '''determines if a point is in the Mandelbrot set based on deciding if,&lt;br /&gt;       after a maximum allowed number of iterations, the absolute value of&lt;br /&gt;       the resulting number is greater or equal to 2.'''&lt;br /&gt;    cdef double z_real = 0., z_imag = 0.&lt;br /&gt;    cdef int i&lt;br /&gt;&lt;br /&gt;    for i in range(0, max_iterations):&lt;br /&gt;        z_real, z_imag = ( z_real*z_real - z_imag*z_imag + real,&lt;br /&gt;                     2*z_real*z_imag + imag )&lt;br /&gt;        if (z_real*z_real + z_imag*z_imag) &amp;gt;= 4:&lt;br /&gt;            return False&lt;br /&gt;    return (z_real*z_real + z_imag*z_imag) &amp;lt; 4&lt;br /&gt;&lt;br /&gt;cdef inline void draw_pixel(x, y, canvas):&lt;br /&gt;    '''Simulates drawing a given pixel in black by drawing a black line&lt;br /&gt;       of length equal to one pixel.'''&lt;br /&gt;    return&lt;br /&gt;    #canvas.create_line(x, y, x+1, y, fill="black")&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The result is even better than I would have expected!&lt;br /&gt;&lt;pre&gt;      505519 function calls in 0.817 CPU seconds&lt;br /&gt;&lt;br /&gt;ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;    11    0.605    0.055    0.676    0.061 mandel2g_cy.pyx:27(create_fractal)&lt;br /&gt;     1    0.128    0.128    0.128    0.128 {_tkinter.create}&lt;br /&gt;504877    0.070    0.000    0.070    0.000 mandel2g_cy.pyx:21(draw_pixel)&lt;br /&gt;    37    0.010    0.000    0.010    0.000 {built-in method call}&lt;br /&gt;    11    0.001    0.000    0.678    0.062 viewer2b.py:20(draw_fractal)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;From 85 seconds (at the beginning of this post) down to 0.8 seconds: a reduction by a factor of 100 ...thank you cython!&amp;nbsp; :-)&lt;br /&gt;&lt;br /&gt;However, increasing the number of iterations to 1000 (from the current    value of 100 used for testing) does increase the time significantly.&lt;br /&gt;&lt;pre&gt;      495235 function calls in 3.872 CPU seconds&lt;br /&gt;&lt;br /&gt;ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;    11    3.653    0.332    3.723    0.338 mandel2g_cy.pyx:27(create_fractal)&lt;br /&gt;     1    0.136    0.136    0.136    0.136 {_tkinter.create}&lt;br /&gt;494593    0.071    0.000    0.071    0.000 mandel2g_cy.pyx:21(draw_pixel)&lt;br /&gt;    37    0.009    0.000    0.009    0.000 {built-in method call}&lt;br /&gt;    11    0.001    0.000    3.726    0.339 viewer2b.py:20(draw_fractal)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;It is probably a good time to put back the drawing to see what the   overall time profile looks like in a more realistic situation.&lt;br /&gt;&lt;pre&gt;      5441165 function calls in 20.747 CPU seconds&lt;br /&gt;&lt;br /&gt;ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;494604    8.682    0.000   14.427    0.000 Tkinter.py:2135(_create)&lt;br /&gt;    11    3.863    0.351   20.572    1.870 mandel2g_cy.pyx:27(create_fractal)&lt;br /&gt;494632    2.043    0.000    5.326    0.000 Tkinter.py:1046(_options)&lt;br /&gt;494657    1.845    0.000    2.861    0.000 Tkinter.py:77(_cnfmerge)&lt;br /&gt;494593    1.548    0.000   16.709    0.000 mandel2g_cy.pyx:21(draw_pixel)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Clearly, the limiting time factor is now the Tkinter based drawing, and not    the other code.  It is time to think of a better drawing strategy.    However, this will have to wait until next post.&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-8913951391285780800?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/8913951391285780800/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=8913951391285780800' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/8913951391285780800'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/8913951391285780800'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2010/01/profiling-adventures-and-cython.html' title='Profiling adventures and cython - Introducing cython'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-1130467121775106715</id><published>2010-01-16T20:48:00.006-04:00</published><updated>2010-01-17T13:42:44.933-04:00</updated><title type='text'>Profiling adventures [and cython]: basic profiling</title><content type='html'>&lt;i&gt;In the previous blog post, I introduced a simple Tkinter-based viewer for the Mandelbrot set.  As I mentioned at that time, the viewer was really too slow to be usable.  In this post, I will start do some basic profiling and start looking for some strategies designed to make it faster.&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The first rule for making an application faster is to do a proper profile rather than guessing.  I make use of the &lt;a href="http://docs.python.org/library/profile.html"&gt;profiler module&lt;/a&gt;, focusing on the main method (&lt;code&gt;draw_fractal()&lt;/code&gt;) which I wish to make faster, and paying a closer look only at the most time-consuming functions/methods.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="py" name="code"&gt;# profile1.py&lt;br /&gt;&lt;br /&gt;import pstats&lt;br /&gt;import cProfile&lt;br /&gt;&lt;br /&gt;from viewer1 import tk, FancyViewer&lt;br /&gt;&lt;br /&gt;def main():&lt;br /&gt;    root = tk.Tk()&lt;br /&gt;    app = FancyViewer(root)&lt;br /&gt;    app.nb_iterations = 100&lt;br /&gt;    for i in range(10):&lt;br /&gt;        app.draw_fractal()&lt;br /&gt;&lt;br /&gt;if __name__ == "__main__":&lt;br /&gt;    cProfile.run("main()", "Profile.prof")&lt;br /&gt;    s = pstats.Stats("Profile.prof")&lt;br /&gt;    s.strip_dirs().sort_stats("time").print_stats(10)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The profile run will call &lt;code&gt;draw_fractal()&lt;/code&gt; once, when &lt;code&gt;app&lt;/code&gt;  is created, with the number of iterations for &lt;code&gt;mendel()&lt;/code&gt; set at 20 (the default) and then call it again 10 times with a larger number of iterations. Running the profiler adds some overhead.  Based on the previous run with no profiles, I would have expected a profiler run to take approximately 75 seconds: a little over 4 seconds for the initial set up and slightly more than 7 seconds for each of the subsequent runs. Instead, what I observe is that&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;69802205 function calls in 115.015 CPU seconds&lt;br /&gt;&lt;br /&gt;  Ordered by: internal time&lt;br /&gt;  List reduced from 60 to 10 due to restriction &amp;lt;10&amp;gt;&lt;br /&gt;&lt;br /&gt;  ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt; 3168000   59.908    0.000   81.192    0.000 mandel1a.py:3(mandel)&lt;br /&gt;57908682   14.005    0.000   14.005    0.000 {abs}&lt;br /&gt;      11   13.387    1.217  114.484   10.408 viewer1.py:23(draw_fractal)&lt;br /&gt;  505184   10.950    0.000   17.672    0.000 Tkinter.py:2135(_create)&lt;br /&gt; 3168001    7.279    0.000    7.279    0.000 {range}&lt;br /&gt;  505212    2.359    0.000    6.220    0.000 Tkinter.py:1046(_options)&lt;br /&gt;  505237    2.169    0.000    3.389    0.000 Tkinter.py:77(_cnfmerge)&lt;br /&gt;  505173    1.457    0.000   19.902    0.000 viewer1.py:17(draw_pixel)&lt;br /&gt; 1010400    0.906    0.000    0.906    0.000 {method 'update' of 'dict' objects}&lt;br /&gt; 1010417    0.817    0.000    0.817    0.000 {_tkinter._flatten}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Clearly, running the profiler adds some overhead.  I should also add that there are variations from run to run done with the profiler, caused by background activities.  As a consequence, I normally run the profiler 3 times and focus on the fastest of the three runs; however I will not bother to do this here: I simply want to start by establishing some rough baseline to identify the main contributors to the relative lack of speed of this program.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;It appears clear that the largest contributor to the overall execution time is &lt;code&gt;mandel()&lt;/code&gt;. Going down the lists of functions that contribute significantly to the overall time, I notice quite a few calls to Tkinter function/methods.  So as to reduce the time to take a given profile, and to focus on &lt;code&gt;mandel()&lt;/code&gt;, I will temporarily eliminate some Tkinter calls by changing &lt;code&gt;draw_pixel()&lt;/code&gt; as follows.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="py" name="code"&gt;def draw_pixel(self, x, y):&lt;br /&gt;    '''Simulates drawing a given pixel in black by drawing a black line&lt;br /&gt;       of length equal to one pixel.'''&lt;br /&gt;    return&lt;br /&gt;    #self.canvas.create_line(x, y, x+1, y, fill="black")&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Also, since I want to establish a rough baseline, I should probably see what happens when I increase the number of iterations from 100 to 1000 for &lt;code&gt;mandel()&lt;/code&gt;, which is what I expect to have to use in many cases to get accurate pictures.  I do this first using Python 2.5&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;465659765 function calls in 574.947 CPU seconds&lt;br /&gt;&lt;br /&gt;   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;  3168000  419.296    0.000  561.467    0.000 mandel1a.py:3(mandel)&lt;br /&gt;458828552  100.464    0.000  100.464    0.000 {abs}&lt;br /&gt;  3168001   41.707    0.000   41.707    0.000 {range}&lt;br /&gt;       11   12.731    1.157  574.341   52.213 viewer1.py:23(draw_fractal)&lt;br /&gt;        1    0.596    0.596    0.596    0.596 {_tkinter.create}&lt;br /&gt;   494593    0.140    0.000    0.140    0.000 viewer1.py:17(draw_pixel)&lt;br /&gt;       37    0.010    0.000    0.010    0.000 {built-in method call}&lt;br /&gt;       11    0.000    0.000    0.001    0.000 Tkinter.py:2135(_create)&lt;br /&gt;       54    0.000    0.000    0.000    0.000 {method 'update' of 'dict' objects}&lt;br /&gt;       39    0.000    0.000    0.000    0.000 Tkinter.py:1046(_options)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ouch!  Close to 10 minutes of running time. However, it is clear that I have accomplished my goal of reducing the importance of Tkinter calls so that I can focus on my own code.   Let's repeat this profiling test using Python 3.1.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;462491974 function calls (462491973 primitive calls) in 506.148 CPU seconds&lt;br /&gt;&lt;br /&gt;   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;  3168000  386.169    0.000  493.823    0.000 mandel1a.py:3(mandel)&lt;br /&gt;458828552  107.654    0.000  107.654    0.000 {built-in method abs}&lt;br /&gt;       11   11.474    1.043  505.439   45.949 viewer1.py:23(draw_fractal)&lt;br /&gt;        1    0.694    0.694    0.694    0.694 {built-in method create}&lt;br /&gt;   494593    0.140    0.000    0.140    0.000 viewer1.py:17(draw_pixel)&lt;br /&gt;       48    0.014    0.000    0.014    0.000 {method 'call' of 'tkapp' objects}&lt;br /&gt;       39    0.000    0.000    0.001    0.000 __init__.py:1032(_options)&lt;br /&gt;       64    0.000    0.000    0.001    0.000 __init__.py:66(_cnfmerge)&lt;br /&gt;      206    0.000    0.000    0.000    0.000 {built-in method isinstance}&lt;br /&gt;      2/1    0.000    0.000  506.148  506.148 {built-in method exec}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;We note that the total time taken is significantly less.  Doing a comparison function by function, two significant differences appear: The built-in function &lt;code&gt;abs&lt;/code&gt; is 7% slower with Python 3.1, which is a bit disappointing.  On the other hand, &lt;code&gt;range&lt;/code&gt; no longer appears as a function in Python 3.1; this appears to be the main contributor to the significant decrease in time when using Python 3.1 as compared with Python 2.5.  This is easily understood: &lt;code&gt;range&lt;/code&gt; in Python 3.1 does not create a list like it did in Python 2.x; it is rather like the old &lt;code&gt;xrange&lt;/code&gt;. This suggest that I modify mandel1a.py to be as follows:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="py" name="code"&gt;# mandel1b.py&lt;br /&gt;&lt;br /&gt;import sys&lt;br /&gt;if sys.version_info &amp;lt; (3,):&lt;br /&gt;    range = xrange&lt;br /&gt;&lt;br /&gt;def mandel(c):&lt;br /&gt;    '''determines if a point is in the Mandelbrot set based on deciding if,&lt;br /&gt;       after 20 iterations, the absolute value of the resulting number is&lt;br /&gt;       greater or equal to 2.'''&lt;br /&gt;    z = 0&lt;br /&gt;    for iter in range(0, 20):&lt;br /&gt;        z = z**2 + c&lt;br /&gt;        if abs(z) &amp;gt;= 2:&lt;br /&gt;            return False&lt;br /&gt;    return abs(z) &amp;lt; 2&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;I also do a similar change to viewer1.py.  From now on, except where otherwise noted, I will focus on using only Python 2.5.  So, after doing this change,     we can run the profiler one more time with the same number of iterations.     The result is as follows: &lt;br /&gt;&lt;pre&gt;462491765 function calls in 503.926 CPU seconds&lt;br /&gt;&lt;br /&gt;   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;  3168000  391.297    0.000  491.642    0.000 mandel1b.py:7(mandel)&lt;br /&gt;458828552  100.345    0.000  100.345    0.000 {abs}&lt;br /&gt;       11   11.409    1.037  503.189   45.744 viewer1.py:23(draw_fractal)&lt;br /&gt;        1    0.726    0.726    0.726    0.726 {_tkinter.create}&lt;br /&gt;   494593    0.136    0.000    0.136    0.000 viewer1.py:17(draw_pixel)&lt;br /&gt;       37    0.009    0.000    0.009    0.000 {built-in method call}&lt;br /&gt;       11    0.000    0.000    0.001    0.000 Tkinter.py:2135(_create)&lt;br /&gt;       54    0.000    0.000    0.000    0.000 {method 'update' of 'dict' objects}&lt;br /&gt;       39    0.000    0.000    0.000    0.000 Tkinter.py:1046(_options)&lt;br /&gt;       64    0.000    0.000    0.001    0.000 Tkinter.py:77(_cnfmerge)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This is now approximately the same as what we had for Python 3.1 as     expected. Moving down on the list of time-consuming functions, we note that     &lt;code&gt;abs&lt;/code&gt; appears to be another function we should look at.     Let's first reduce the number of iterations inside &lt;code&gt;mandel&lt;/code&gt;     to 100, so that a profiling run does not take as long but that proper attention is still focuses on &lt;code&gt;mandel&lt;/code&gt; as well as     &lt;code&gt;abs&lt;/code&gt;.  Here's the result from a typical run to use as     a new baseline:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;61582475 function calls in 81.998 CPU seconds&lt;br /&gt;&lt;br /&gt;  ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt; 3168000   55.347    0.000   69.054    0.000 mandel1b.py:7(mandel)&lt;br /&gt;57908682   13.706    0.000   13.706    0.000 {abs}&lt;br /&gt;      11   11.591    1.054   80.773    7.343 viewer1.py:23(draw_fractal)&lt;br /&gt;       1    1.213    1.213    1.213    1.213 {_tkinter.create}&lt;br /&gt;  505173    0.125    0.000    0.125    0.000 viewer1.py:17(draw_pixel)&lt;br /&gt;      37    0.011    0.000    0.011    0.000 {built-in method call}&lt;br /&gt;      11    0.000    0.000    0.001    0.000 Tkinter.py:2135(_create)&lt;br /&gt;       4    0.000    0.000    0.000    0.000 {posix.stat}&lt;br /&gt;       3    0.000    0.000    0.000    0.000 Tkinter.py:1892(_setup)&lt;br /&gt;      39    0.000    0.000    0.000    0.000 Tkinter.py:1046(_options)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Since a fair bit of time is spent inside &lt;code&gt;abs()&lt;/code&gt;, perhIaps&amp;nbsp; could speed things up by using another method.  The way that we     approximate the Mandlebrot set is by iterating over a number of time     and checking if the absolute value of the complex number is greater than     2; if it is, then it can be proven that subsequent iterations will yield     larger and larger values which means that the number we are considering is &lt;b&gt;not&lt;/b&gt; in the Mandelbrot set.  Since taking an absolute value of a     complex number involves taking a square root, perhaps we can speed things     up by not taking the square root.  Let's implement this and try it out.&lt;br /&gt;&lt;br /&gt;&lt;pre class="py" name="code"&gt;# mandel1c.py&lt;br /&gt;&lt;br /&gt;import sys&lt;br /&gt;if sys.version_info &amp;lt; (3,):&lt;br /&gt;    range = xrange&lt;br /&gt;&lt;br /&gt;def mandel(c, max_iterations=20):&lt;br /&gt;    '''determines if a point is in the Mandelbrot set based on deciding if,&lt;br /&gt;       after a maximum allowed number of iterations, the absolute value of&lt;br /&gt;       the resulting number is greater or equal to 2.'''&lt;br /&gt;    z = 0&lt;br /&gt;    for iter in range(0, max_iterations):&lt;br /&gt;        z = z**2 + c&lt;br /&gt;        z_sq = z.real**2 + z.imag**2&lt;br /&gt;        if z_sq &amp;gt;= 4:&lt;br /&gt;            return False&lt;br /&gt;    return z_sq &amp;lt; 4&lt;br /&gt;&lt;/pre&gt;&lt;pre&gt;3673150 function calls in 84.807 CPU seconds&lt;br /&gt;&lt;br /&gt; ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;br /&gt;3168000   73.210    0.000   73.210    0.000 mandel1c.py:7(mandel)&lt;br /&gt;     11   10.855    0.987   84.204    7.655 viewer1.py:23(draw_fractal)&lt;br /&gt;      1    0.593    0.593    0.593    0.593 {_tkinter.create}&lt;br /&gt; 504530    0.137    0.000    0.137    0.000 viewer1.py:17(draw_pixel)&lt;br /&gt;     37    0.009    0.000    0.009    0.000 {built-in method call}&lt;br /&gt;     11    0.000    0.000    0.001    0.000 Tkinter.py:2135(_create)&lt;br /&gt;     54    0.000    0.000    0.000    0.000 {method 'update' of 'dict' objects}&lt;br /&gt;     39    0.000    0.000    0.000    0.000 Tkinter.py:1046(_options)&lt;br /&gt;     64    0.000    0.000    0.001    0.000 Tkinter.py:77(_cnfmerge)&lt;br /&gt;     22    0.000    0.000    0.001    0.000 Tkinter.py:1172(_configure)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The result is worse than before, even though the total number of function calls     has almost been cut in half!  Actually, this should not come as a total     surprise: &lt;code&gt;abs&lt;/code&gt; is a Python built-in function, which has     been already optimized in C.  Extracting the real and imaginary parts explictly     like we have done is bound to be a time-consuming operation when performed     in pure Python as opposed to C.  At this point, we might be tempted     to convert complex numbers everywhere into pairs of real numbers so as to     reduce the overhead of dealing with complex numbers ... but this would     not have any significant effect on the overall time.  &lt;small&gt;[Those curious     may want to try ... I've done it and it's not worth reporting in details.]&amp;nbsp;&lt;/small&gt;&lt;br /&gt;&lt;small&gt;&amp;nbsp;&lt;/small&gt; &lt;br /&gt;Clearly, I need a different strategy if we are to reduce significantly     the execution time.  It is time to introduce cython.     However, this will have to wait until the next blog post!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-1130467121775106715?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/1130467121775106715/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=1130467121775106715' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/1130467121775106715'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/1130467121775106715'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2010/01/profiling-adventures-and-cython-basic.html' title='Profiling adventures [and cython]: basic profiling'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-8141868372013428488</id><published>2010-01-15T22:17:00.003-04:00</published><updated>2010-01-15T22:22:02.092-04:00</updated><title type='text'>Profiling adventures and cython - setting the stage</title><content type='html'>&lt;b&gt;Summary&lt;/b&gt;  This post is the first in a series dedicated to&lt;br /&gt;examining the use of profiling and, eventually, using cython, as a&lt;br /&gt;means to improve greatly the speed of an application.  The intended&lt;br /&gt;audience is for programmers who have never done any profiling and/or&lt;br /&gt;never used cython before. Note that we will not make use of cython&lt;br /&gt;until the third post in this series.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_KpSgL5zKAvE/S1EgLYx25EI/AAAAAAAAACQ/UnYTtRojL78/s1600-h/colored1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/_KpSgL5zKAvE/S1EgLYx25EI/AAAAAAAAACQ/UnYTtRojL78/s320/colored1.png" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;h4&gt;Preamble&lt;br /&gt;&lt;/h4&gt;Python is a great multi-purpose language which is really fun to use. However, it is sometimes too slow for some applications.  Since I only program for fun, I had never really faced a situation where I found Python's speed to be truly a limiting factor - at least, not until a few weeks ago when I did some exploration of a&amp;nbsp; &lt;a href="http://aroberge.blogspot.com/2009/12/17x17-challenge.html"&gt;four-colouring grid problem I talked about&lt;/a&gt;. I started exploring ways to speed things up using only Python and trying to come up with different algorithms, but every one I tried was just too slow.  So, I decided it was time to take the plunge and do something different.  After considering various alternatives, like using &lt;a href="http://code.google.com/p/shedskin/"&gt;shedskin&lt;/a&gt; or&lt;br /&gt;attempting to write a C extension (which I found too daunting since I don't know C), I decided to try to use&amp;nbsp; &lt;a href="http://cython.org/"&gt;cython&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://cython.org/"&gt;cython&lt;/a&gt;, for those that don't know it, is a Python look-alike language that claims&lt;br /&gt;&lt;blockquote&gt;&lt;i&gt;to make writing C extensions for the Python language&lt;/i&gt;&lt;br /&gt;&lt;i&gt;as easy as Python itself.&lt;/i&gt;&lt;br /&gt;&lt;/blockquote&gt;&lt;i&gt;&lt;/i&gt;&lt;br /&gt;After looking at a few examples on the web, I concluded that such a rather bold statement might very well be true and that it was worthwhile trying it out on a more complex example.  Furthermore, I thought it might be of interest to record what I've done in a series of blog posts, as a more detailed example than what I had found so far on the web. As I was wondering if an esoteric problem like the four-colouring grid challenge mentioned previously was a good candidate to use as an example, by sheer serendipity, I came accross a &lt;a href="http://www.reddit.com/r/Python/comments/ajeey/mandelbrot_set_in_python_tkinter/"&gt;link on reddit&lt;/a&gt; by a new programmer about his &lt;a href="http://prezjordan.tumblr.com/post/277984651/mandelbrot-set-in-python"&gt;simple Mandelbrot viewer&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Who does not like fractals? ... Furthermore, I have never written a fractal viewer.   This seemed like a good time to write one.  So, my goal at the end of this series of posts, is to have a "nice" (for some definition of "nice") fractal viewer that is fast enough for explorations of the &lt;a href="http://en.wikipedia.org/wiki/Mandelbrot_set"&gt;Mandelbrot set&lt;/a&gt;.&amp;nbsp; In addition, in order to make it easy for anyone having Python on their system to follow along and try their own variation, I decided to stick by the following constraints:&lt;br /&gt;&lt;ul&gt;&lt;b&gt;    &lt;/b&gt;&lt;li&gt;&lt;b&gt;With the exception of cython, I will only use modules found in the&lt;br /&gt;standard library.   This means using Tkinter for the GUI.&lt;br /&gt;&lt;/b&gt;&lt;/li&gt;&lt;b&gt;    &lt;/b&gt;&lt;li&gt;&lt;b&gt;The program should work using Python 2.5+ (including Python 3).&lt;br /&gt;&lt;/b&gt;&lt;/li&gt;&lt;/ul&gt;&lt;b&gt;&lt;/b&gt;&lt;br /&gt;So, without further ado, and based on the example found on the reddit link I mentioned, here's a very basic fractal viewer that can be used as a starting point.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="py" name="code"&gt;''' mandel1.py&lt;br /&gt;&lt;br /&gt;Mandelbrot set drawn in black and white.'''&lt;br /&gt;&lt;br /&gt;import time&lt;br /&gt;&lt;br /&gt;import sys&lt;br /&gt;if sys.version_info &amp;lt; (3,):&lt;br /&gt;    import Tkinter as tk&lt;br /&gt;else:&lt;br /&gt;    import tkinter as tk&lt;br /&gt;&lt;br /&gt;def mandel(c):&lt;br /&gt;    '''determines if a point is in the Mandelbrot set based on deciding if,&lt;br /&gt;       after 20 iterations, the absolute value of the resulting number is&lt;br /&gt;       greater or equal to 2.'''&lt;br /&gt;    z = 0&lt;br /&gt;    for iter in range(0, 20):&lt;br /&gt;        z = z**2 + c&lt;br /&gt;        if abs(z) &amp;gt;= 2:&lt;br /&gt;            return False&lt;br /&gt;    return abs(z) &amp;lt; 2&lt;br /&gt;&lt;br /&gt;class Viewer(object):&lt;br /&gt;    def __init__(self, parent, width=500, height=500):&lt;br /&gt;        self.canvas = tk.Canvas(parent, width=width, height=height)&lt;br /&gt;        self.width = width&lt;br /&gt;        self.height = height&lt;br /&gt;&lt;br /&gt;        # the following "shift" variables are used to center the drawing&lt;br /&gt;        self.shift_x = 0.5*self.width&lt;br /&gt;        self.shift_y = 0.5*self.height&lt;br /&gt;        self.scale = 0.01&lt;br /&gt;&lt;br /&gt;        self.canvas.pack()&lt;br /&gt;        self.draw_fractal()&lt;br /&gt;&lt;br /&gt;    def draw_pixel(self, x, y):&lt;br /&gt;        '''Simulates drawing a given pixel in black by drawing a black line&lt;br /&gt;           of length equal to one pixel.'''&lt;br /&gt;        self.canvas.create_line(x, y, x+1, y, fill="black")&lt;br /&gt;&lt;br /&gt;    def draw_fractal(self):&lt;br /&gt;        '''draws a fractal picture'''&lt;br /&gt;        begin = time.time()&lt;br /&gt;        print("Inside draw_fractal.")&lt;br /&gt;        for x in range(0, self.width):&lt;br /&gt;            real = (x - self.shift_x)*self.scale&lt;br /&gt;            for y in range(0, self.height):&lt;br /&gt;                imag = (y - self.shift_y)*self.scale&lt;br /&gt;                c = complex(real, imag)&lt;br /&gt;                if mandel(c):&lt;br /&gt;                    self.draw_pixel(x, self.height - y)&lt;br /&gt;        print("Time taken for calculating and drawing = %s" %&lt;br /&gt;                                                (time.time() - begin))&lt;br /&gt;&lt;br /&gt;if __name__ == "__main__":&lt;br /&gt;    print("Starting...")&lt;br /&gt;    root = tk.Tk()&lt;br /&gt;    app = Viewer(root)&lt;br /&gt;    root.mainloop()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;At this point, perhaps a few comments about the program might be useful &lt;br /&gt;&lt;ol&gt;&lt;li&gt;I have tried to write the code in the most straightforward and pythonic way, with no thought given to making calculations fast. It should be remembered that this is just a starting point: first we make it work, then, if needed, we make it fast.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;The function &lt;code&gt;mandel()&lt;/code&gt; is the simplest translation of the Mandelbrot fractal iteration into Python code that I could come up with.  The fact that Python has a built-in complex type makes it very easy to implement the standard Mandelbrot set algorithm.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;I have taken the maximum number of iterations inside &lt;code&gt;mandel()&lt;/code&gt; to be 20, the same value used in the post I mentioned before. According to the very simple method used to time the application, it takes about 2 seconds on my computer to draw a simple picture. This is annoying slow.  Furthermore, by looking at the resulting picture, and trying out with different number of iterations in &lt;code&gt;mandel()&lt;/code&gt;, it is clear that 20 iterations is not sufficient to adaquately represent the Mandelbrot set; this is especially noticeable when exploring smaller regions of the complex plane. A more realistic value might be to take 100 if not 1000 iterations which takes too long to be practical.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Tkinter's canvas does not have a method to set the colour of individual pixels.  We can simulate such a method by drawing a line (for which there is a primitive method) of length 1.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;The screen vertical coordinates ("y") increase in values from the top towards the bottom, in opposite direction to the usual vertical coordinates in the complex plane.  While the picture produced is vertically symmetric about the x-axis, I nonetheless wrote the code so that the inversion of direction was properly handled.&lt;br /&gt;&lt;/li&gt;&lt;/ol&gt;This basic application is not really useful as a tool for exploring     the Mandelbrot set, as the region of the complex plane it displays     is fixed.  However, it is useful to start with something simple     like this as a first prototype.  Once we know it is working     we can move on to a better second version.  So, let's write a fancier     fractal viewer following the outline below:&lt;br /&gt;&lt;br /&gt;&lt;pre class="py" name="code"&gt;class Viewer(object):&lt;br /&gt;    '''Base class viewer to display fractals'''&lt;br /&gt;&lt;br /&gt;        # The viewer should be able to enlarge ("zoom in") various regions&lt;br /&gt;        # of the complex plane.  I will implement this&lt;br /&gt;        # using keyboard shortcuts.&lt;br /&gt;        #&lt;br /&gt;        self.parent.bind("+", self.zoom_in)&lt;br /&gt;        self.parent.bind("-", self.zoom_out)&lt;br /&gt;    def zoom_in(self, event):&lt;br /&gt;    def zoom_out(self, event):&lt;br /&gt;    def change_scale(self, scale):&lt;br /&gt;&lt;br /&gt;        #  Since one might want to "zoom in" quickly in some regions,&lt;br /&gt;        # and then be able to do finer scale adjustments,&lt;br /&gt;        # I will use keyboard shortcuts to enable switching back&lt;br /&gt;        # and forth between two possible zooming mode.&lt;br /&gt;        # A better application might give the user more control&lt;br /&gt;        # over the zooming scale.&lt;br /&gt;        self.parent.bind("n", self.normal_zoom)&lt;br /&gt;        self.parent.bind("b", self.bigger_zoom)&lt;br /&gt;    def normal_zoom(self, event, scale=1):&lt;br /&gt;    def bigger_zoom(self, event):&lt;br /&gt;&lt;br /&gt;        # Set the maximum number of iterations via a keyboard-triggered event&lt;br /&gt;        self.parent.bind("i", self.set_max_iter)&lt;br /&gt;    def set_max_iter(self, event):&lt;br /&gt;&lt;br /&gt;        # Like what is done with google maps and other&lt;br /&gt;        # similar applications, we should be able to move the image&lt;br /&gt;        # to look at various regions of interest in the complex plane.&lt;br /&gt;        # I will implement this using mouse controls.&lt;br /&gt;        self.parent.bind("&amp;lt;button-1&gt;", self.mouse_down)&lt;br /&gt;        self.parent.bind("&amp;lt;button1-motion&gt;", self.mouse_motion)&lt;br /&gt;        self.parent.bind("&amp;lt;button1-buttonrelease&gt;", self.mouse_up)&lt;br /&gt;    def mouse_down(self, event):&lt;br /&gt;    def mouse_motion(self, event):&lt;br /&gt;    def mouse_up(self, event):&lt;br /&gt;&lt;br /&gt;        # Presuming that "nice pictures" will be eventually produced,&lt;br /&gt;        # and that it might be desired to reproduce them,&lt;br /&gt;        # I will include some information about the region of the&lt;br /&gt;        # complex plane currently displayed.&lt;br /&gt;    def info(self):&lt;br /&gt;        '''information about fractal location'''&lt;br /&gt;&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;Furthermore, while I plan to use proper profiling tools, I will nonetheless display some basic timing information as part of the GUI as a quick evaluation&lt;br /&gt;of the speed of the application.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Finally, since I expect that both the function &lt;code&gt;mandel()&lt;/code&gt; and the drawing method &lt;code&gt;draw_fractal&lt;/code&gt; to be the speed-limiting factors, I will leave them out of the fractal viewer and work on them separately.  If it turns out that the profiling information obtained indicates otherwise, I will revisit this hypothesis.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;Here is a second prototype for my fractal viewer, having the features     described above.&lt;br /&gt;&lt;br /&gt;&lt;pre class="py" name="code"&gt;''' viewer.py&lt;br /&gt;&lt;br /&gt;Base class viewer for fractals.'''&lt;br /&gt;&lt;br /&gt;import sys&lt;br /&gt;if sys.version_info &amp;lt; (3,):&lt;br /&gt;    import Tkinter as tk&lt;br /&gt;    import tkSimpleDialog as tk_dialog&lt;br /&gt;else:&lt;br /&gt;    import tkinter as tk&lt;br /&gt;    from tkinter import simpledialog as tk_dialog&lt;br /&gt;&lt;br /&gt;class Viewer(object):&lt;br /&gt;    '''Base class viewer to display fractals'''&lt;br /&gt;&lt;br /&gt;    def __init__(self, parent, width=600, height=480,&lt;br /&gt;                 min_x=-2.5, min_y=-1.5, max_x=1.):&lt;br /&gt;&lt;br /&gt;        self.parent = parent&lt;br /&gt;        self.canvas_width = width&lt;br /&gt;        self.canvas_height = height&lt;br /&gt;&lt;br /&gt;        # The following are drawing boundaries in the complex plane&lt;br /&gt;        self.min_x = min_x&lt;br /&gt;        self.min_y = min_y&lt;br /&gt;        self.max_x = max_x&lt;br /&gt;        self.calculate_pixel_size()&lt;br /&gt;        self.max_y = self.min_y + self.canvas_height*self.pixel_size&lt;br /&gt;&lt;br /&gt;        self.calculating = False&lt;br /&gt;        self.nb_iterations = 20&lt;br /&gt;        self.normal_zoom(None)&lt;br /&gt;&lt;br /&gt;        self.canvas = tk.Canvas(parent, width=width, height=height)&lt;br /&gt;        self.canvas.pack()&lt;br /&gt;        self.status = tk.Label(self.parent, text="", bd=1, relief=tk.SUNKEN,&lt;br /&gt;                               anchor=tk.W)&lt;br /&gt;        self.status.pack(side=tk.BOTTOM, fill=tk.X)&lt;br /&gt;        self.status2 = tk.Label(self.parent, text=self.info(), bd=1,&lt;br /&gt;                                relief=tk.SUNKEN, anchor=tk.W)&lt;br /&gt;        self.status2.pack(side=tk.BOTTOM, fill=tk.X)&lt;br /&gt;&lt;br /&gt;        # We change the size of the image using the keyboard.&lt;br /&gt;        self.parent.bind("+", self.zoom_in)&lt;br /&gt;        self.parent.bind("-", self.zoom_out)&lt;br /&gt;        self.parent.bind("n", self.normal_zoom)&lt;br /&gt;        self.parent.bind("b", self.bigger_zoom)&lt;br /&gt;&lt;br /&gt;        # Set the maximum number of iterations via a keyboard-triggered event&lt;br /&gt;        self.parent.bind("i", self.set_max_iter)&lt;br /&gt;&lt;br /&gt;        # We move the canvas using the mouse.&lt;br /&gt;        self.translation_line = None&lt;br /&gt;        self.parent.bind("&amp;lt;button-1&gt;", self.mouse_down)&lt;br /&gt;        self.parent.bind("&amp;lt;button1-motion&gt;", self.mouse_motion)&lt;br /&gt;        self.parent.bind("&amp;lt;button1-buttonrelease&gt;", self.mouse_up)&lt;br /&gt;&lt;br /&gt;        self.draw_fractal()  # Needs to be implemented by subclass&lt;br /&gt;&lt;br /&gt;    def info(self):&lt;br /&gt;        '''information about fractal location'''&lt;br /&gt;        return "Location: (%f, %f) to (%f, %f)" %(self.min_x, self.min_y,&lt;br /&gt;                                                  self.max_x, self.max_y)&lt;br /&gt;&lt;br /&gt;    def calculate_pixel_size(self):&lt;br /&gt;        '''Calculates the size of a (square) pixel in complex plane&lt;br /&gt;        coordinates based on the canvas_width.'''&lt;br /&gt;        self.pixel_size = 1.*(self.max_x - self.min_x)/self.canvas_width&lt;br /&gt;        return&lt;br /&gt;&lt;br /&gt;    def mouse_down(self, event):&lt;br /&gt;        '''records the x and y positions of the mouse when the left button&lt;br /&gt;           is clicked.'''&lt;br /&gt;        self.start_x = self.canvas.canvasx(event.x)&lt;br /&gt;        self.start_y = self.canvas.canvasy(event.y)&lt;br /&gt;&lt;br /&gt;    def mouse_motion(self, event):&lt;br /&gt;        '''keep track of the mouse motion by drawing a line from its&lt;br /&gt;           starting point to the current point.'''&lt;br /&gt;        x = self.canvas.canvasx(event.x)&lt;br /&gt;        y = self.canvas.canvasy(event.y)&lt;br /&gt;&lt;br /&gt;        if (self.start_x != event.x)  and (self.start_y != event.y) :&lt;br /&gt;            self.canvas.delete(self.translation_line)&lt;br /&gt;            self.translation_line = self.canvas.create_line(&lt;br /&gt;                                self.start_x, self.start_y, x, y, fill="orange")&lt;br /&gt;            self.canvas.update_idletasks()&lt;br /&gt;&lt;br /&gt;    def mouse_up(self, event):&lt;br /&gt;        '''Moves the canvas based on the mouse motion'''&lt;br /&gt;        dx = (self.start_x - event.x)*self.pixel_size&lt;br /&gt;        dy = (self.start_y - event.y)*self.pixel_size&lt;br /&gt;        self.min_x += dx&lt;br /&gt;        self.max_x += dx&lt;br /&gt;        # y-coordinate in complex plane run in opposite direction from&lt;br /&gt;        # screen coordinates&lt;br /&gt;        self.min_y -= dy&lt;br /&gt;        self.max_y -= dy&lt;br /&gt;        self.canvas.delete(self.translation_line)&lt;br /&gt;        self.status.config(text="Moving the fractal.  Please wait.")&lt;br /&gt;        self.status.update_idletasks()&lt;br /&gt;        self.status2.config(text=self.info())&lt;br /&gt;        self.status2.update_idletasks()&lt;br /&gt;        self.draw_fractal()&lt;br /&gt;&lt;br /&gt;    def normal_zoom(self, event, scale=1):&lt;br /&gt;        '''Sets the zooming in/out scale to its normal value'''&lt;br /&gt;        if scale==1:&lt;br /&gt;            self.zoom_info = "[normal zoom]"&lt;br /&gt;        else:&lt;br /&gt;            self.zoom_info = "[faster zoom]"&lt;br /&gt;        if event is not None:&lt;br /&gt;            self.status.config(text=self.zoom_info)&lt;br /&gt;            self.status.update_idletasks()&lt;br /&gt;        self.zoom_in_scale = 0.1&lt;br /&gt;        self.zoom_out_scale = -0.125&lt;br /&gt;&lt;br /&gt;    def bigger_zoom(self, event):&lt;br /&gt;        '''Increases the zooming in/out scale from its normal value'''&lt;br /&gt;        self.normal_zoom(event, scale=3)&lt;br /&gt;        self.zoom_in_scale = 0.3&lt;br /&gt;        self.zoom_out_scale = -0.4&lt;br /&gt;&lt;br /&gt;    def zoom_in(self, event):&lt;br /&gt;        '''decreases the size of the region of the complex plane displayed'''&lt;br /&gt;        if self.calculating:&lt;br /&gt;            return&lt;br /&gt;        self.status.config(text="Zooming in.  Please wait.")&lt;br /&gt;        self.status.update_idletasks()&lt;br /&gt;        self.change_scale(self.zoom_in_scale)&lt;br /&gt;&lt;br /&gt;    def zoom_out(self, event):&lt;br /&gt;        '''increases the size of the region of the complex plane displayed'''&lt;br /&gt;        if self.calculating:&lt;br /&gt;            return&lt;br /&gt;        self.status.config(text="Zooming out.  Please wait.")&lt;br /&gt;        self.status.update_idletasks()&lt;br /&gt;        self.change_scale(self.zoom_out_scale)&lt;br /&gt;&lt;br /&gt;    def change_scale(self, scale):&lt;br /&gt;        '''changes the size of the region of the complex plane displayed and&lt;br /&gt;           redraws'''&lt;br /&gt;        if self.calculating:&lt;br /&gt;            return&lt;br /&gt;        dx = (self.max_x - self.min_x)*scale&lt;br /&gt;        dy = (self.max_y - self.min_y)*scale&lt;br /&gt;        self.min_x += dx&lt;br /&gt;        self.max_x -= dx&lt;br /&gt;        self.min_y += dy&lt;br /&gt;        self.max_y -= dy&lt;br /&gt;        self.calculate_pixel_size()&lt;br /&gt;        self.draw_fractal()&lt;br /&gt;&lt;br /&gt;    def set_max_iter(self, event):&lt;br /&gt;        '''set maximum number of iterations'''&lt;br /&gt;        i = tk_dialog.askinteger('title', 'prompt')&lt;br /&gt;        if i is not None:&lt;br /&gt;            self.nb_iterations = i&lt;br /&gt;            self.status.config(text="Redrawing.  Please wait.")&lt;br /&gt;            self.status.update_idletasks()&lt;br /&gt;            self.draw_fractal()&lt;br /&gt;&lt;br /&gt;    def draw_fractal(self):&lt;br /&gt;        '''draws a fractal on the canvas'''&lt;br /&gt;        raise NotImplementedError&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;I move the Mandelbrot set calculation in a separate file.&lt;br /&gt;&lt;br /&gt;&lt;pre class="py" name="code"&gt;# mandel1a.py&lt;br /&gt;&lt;br /&gt;def mandel(c, max_iterations=20):&lt;br /&gt;    '''determines if a point is in the Mandelbrot set based on deciding if,&lt;br /&gt;       after a maximum allowed number of iterations, the absolute value of&lt;br /&gt;       the resulting number is greater or equal to 2.'''&lt;br /&gt;    z = 0&lt;br /&gt;    for iter in range(0, max_iterations):&lt;br /&gt;        z = z**2 + c&lt;br /&gt;        if abs(z) &amp;gt;= 2:&lt;br /&gt;            return False&lt;br /&gt;    return abs(z) &amp;lt; 2&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And, finally, I implement the missing functions for the viewer in     a new main application.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="py" name="code"&gt;# viewer1.py&lt;br /&gt;&lt;br /&gt;from mandel1a import mandel&lt;br /&gt;from viewer import Viewer&lt;br /&gt;import time&lt;br /&gt;&lt;br /&gt;import sys&lt;br /&gt;if sys.version_info &amp;amp;lt; (3,):&lt;br /&gt;    import Tkinter as tk&lt;br /&gt;    range = xrange&lt;br /&gt;else:&lt;br /&gt;    import tkinter as tk&lt;br /&gt;&lt;br /&gt;class FancyViewer(Viewer):&lt;br /&gt;    '''Application to display fractals'''&lt;br /&gt;&lt;br /&gt;    def draw_pixel(self, x, y):&lt;br /&gt;        '''Simulates drawing a given pixel in black by drawing a black line&lt;br /&gt;           of length equal to one pixel.'''&lt;br /&gt;        self.canvas.create_line(x, y, x+1, y, fill="black")&lt;br /&gt;&lt;br /&gt;    def draw_fractal(self):&lt;br /&gt;        '''draws a fractal on the canvas'''&lt;br /&gt;        self.calculating = True&lt;br /&gt;        begin = time.time()&lt;br /&gt;        # clear the canvas&lt;br /&gt;        self.canvas.create_rectangle(0, 0, self.canvas_width,&lt;br /&gt;                                    self.canvas_height, fill="white")&lt;br /&gt;        for x in range(0, self.canvas_width):&lt;br /&gt;            real = self.min_x + x*self.pixel_size&lt;br /&gt;            for y in range(0, self.canvas_height):&lt;br /&gt;                imag = self.min_y + y*self.pixel_size&lt;br /&gt;                c = complex(real, imag)&lt;br /&gt;                if mandel(c, self.nb_iterations):&lt;br /&gt;                    self.draw_pixel(x, self.canvas_height - y)&lt;br /&gt;        self.status.config(text="Time required = %.2f s  [%s iterations]  %s" %(&lt;br /&gt;                                (time.time() - begin), self.nb_iterations,&lt;br /&gt;                                                                self.zoom_info))&lt;br /&gt;        self.status2.config(text=self.info())&lt;br /&gt;        self.status2.update_idletasks()&lt;br /&gt;        self.calculating = False&lt;br /&gt;&lt;br /&gt;if __name__ == "__main__":&lt;br /&gt;    root = tk.Tk()&lt;br /&gt;    app = FancyViewer(root)&lt;br /&gt;    root.mainloop()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;Let me conclude with few black and white pictures obtained using     this program, which, if you look at the time, highlight the need for     something faster. First for 20 iterations, drawn in 4 seconds.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_KpSgL5zKAvE/S1Egcgxbc8I/AAAAAAAAACY/QmpuH1n_PT8/s1600-h/bw_20.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/_KpSgL5zKAvE/S1Egcgxbc8I/AAAAAAAAACY/QmpuH1n_PT8/s640/bw_20.png" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Then, for 100 interations - better image, but 7 seconds to draw...&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_KpSgL5zKAvE/S1EgpUsXVHI/AAAAAAAAACg/MXXt8VJ1KEI/s1600-h/bw_100.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/_KpSgL5zKAvE/S1EgpUsXVHI/AAAAAAAAACg/MXXt8VJ1KEI/s640/bw_100.png" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Next post, I'll start profiling the application and make it faster.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-8141868372013428488?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/8141868372013428488/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=8141868372013428488' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/8141868372013428488'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/8141868372013428488'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2010/01/profiling-adventures-and-cython-setting.html' title='Profiling adventures and cython - setting the stage'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_KpSgL5zKAvE/S1EgLYx25EI/AAAAAAAAACQ/UnYTtRojL78/s72-c/colored1.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-5709612558245963798</id><published>2010-01-10T17:39:00.000-04:00</published><updated>2010-01-10T17:39:54.918-04:00</updated><title type='text'>Python + cython: faster than C?</title><content type='html'>Now that I have your attention... ;-) &lt;br /&gt;&lt;br /&gt;I've been playing around with &lt;a href="http://cython.org/"&gt;cython&lt;/a&gt; and will write more about it soon.&amp;nbsp; But I could not resist doing a quick test when I read &lt;a href="http://prag-matism.blogspot.com/2010/01/micro-benchmark-c-vs-java-nth-fibonacci.html"&gt;this post&lt;/a&gt;, comparing C and Java on some toy micro benchmark.&lt;br /&gt;&lt;br /&gt;First, the result:&lt;br /&gt;&lt;br /&gt;andre$ gcc -o fib -O3 fib.c&lt;br /&gt;andre$ time ./fib&lt;br /&gt;433494437&lt;br /&gt;&lt;br /&gt;real&amp;nbsp;&amp;nbsp;&amp;nbsp; 0m3.765s&lt;br /&gt;user&amp;nbsp;&amp;nbsp;&amp;nbsp; 0m3.463s&lt;br /&gt;sys&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0m0.028s&lt;br /&gt;&lt;br /&gt;andre$ time python fib_test.py&lt;br /&gt;433494437&lt;br /&gt;&lt;br /&gt;real&amp;nbsp;&amp;nbsp;&amp;nbsp; 0m2.953s&lt;br /&gt;user&amp;nbsp;&amp;nbsp;&amp;nbsp; 0m2.452s&lt;br /&gt;sys&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0m0.380s &lt;br /&gt;&lt;br /&gt;Yes, Python+cython is faster than C!&amp;nbsp; :-) &lt;br /&gt;&lt;br /&gt;Now, the code.&amp;nbsp; First, the C program taken straight from this post, except for a different, more meaningful value for "num" ;-)&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;#include &lt;stdio.h&gt;&lt;br /&gt;&lt;br /&gt;double fib(int num)&lt;br /&gt;{&lt;br /&gt;   if (num &amp;lt;= 1)&lt;br /&gt;       return 1;&lt;br /&gt;   else&lt;br /&gt;       return fib(num-1) + fib(num-2);&lt;br /&gt; }&lt;br /&gt; int main(void)&lt;br /&gt;{&lt;br /&gt;     printf("%.0f\n", fib(42));&lt;br /&gt;    return 0;&lt;br /&gt; }&lt;br /&gt;&lt;/stdio.h&gt;&lt;/pre&gt;&lt;br /&gt;Next, the cython code ... &lt;br /&gt;&lt;br /&gt;&lt;pre&gt;# fib.pyx&lt;br /&gt;&lt;br /&gt;cdef inline int fib(int num):&lt;br /&gt;    if (num &amp;lt;= 1):&lt;br /&gt;        return 1&lt;br /&gt;    else:&lt;br /&gt;        return fib(num-1) + fib(num-2)&lt;br /&gt;&lt;br /&gt;def fib_import (int num):&lt;br /&gt;    return fib(num)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;... and the Python file that calls it&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;# fib_test.py&lt;br /&gt;&lt;br /&gt;import pyximport&lt;br /&gt;pyximport.install()&lt;br /&gt;&lt;br /&gt;from fib import fib_import&lt;br /&gt;&lt;br /&gt;print fib_import(42)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;I know, I declared fib() to be of type "double" in the C code (like what was done in the original post) and "int" in the cython code; however, if I declare it to be of type "int" in the C code, I get -0 as the answer instead of 433494437.&amp;nbsp; I could declare it to be of type "unsigned int" ... but even when I did that, the Python code was marginally faster.&lt;/li&gt;&lt;li&gt;If I declared fib to be of type "double" in the cython code, it was slightly slower than the corresponding C code.&amp;nbsp; However, what Python user would ever think of an integer variable as a floating point number! ;-) &lt;br /&gt;&lt;/li&gt;&lt;li&gt;What is most impressive is that the cython code is NOT pre-compiled, unlike the C code.&amp;nbsp; However, to be fair ... I did run it a few times and took the best result, and it was always slower the first time.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Yes, it is silly to compare such micro-benchmarks ... but it can be fun, no? ;-)&lt;/li&gt;&lt;/ol&gt;More seriously: using cython is a relatively painless way to significantly increase the speed of Python applications ... without having to learn a totally different language.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-5709612558245963798?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/5709612558245963798/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=5709612558245963798' title='11 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/5709612558245963798'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/5709612558245963798'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2010/01/python-cython-faster-than-c.html' title='Python + cython: faster than C?'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>11</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-285135963663253253</id><published>2010-01-02T21:24:00.000-04:00</published><updated>2010-01-02T21:24:14.316-04:00</updated><title type='text'>Rur-ple is alive again!</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://code.google.com/p/rur-ple/logo?logo_id=1262453167" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://code.google.com/p/rur-ple/logo?logo_id=1262453167" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;Thanks to some wonderful work by some developers who joined the project, rur-ple is moving forward again. :-)&amp;nbsp; Its &lt;a href="http://code.google.com/p/rur-ple/"&gt;new home&lt;/a&gt; also features a brand new logo for it displayed above.&lt;br /&gt;&lt;br /&gt;Thanks in particular go to &lt;a href="http://fred.dao2.com/"&gt;Frederic Muller&lt;/a&gt; who is moving things along rather nicely.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-285135963663253253?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/285135963663253253/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=285135963663253253' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/285135963663253253'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/285135963663253253'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2010/01/rur-ple-is-alive-again.html' title='Rur-ple is alive again!'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-5603167416470443360</id><published>2009-12-25T10:37:00.000-04:00</published><updated>2009-12-25T10:37:48.180-04:00</updated><title type='text'>The 17x17 challenge: setting up the grid</title><content type='html'>In the previous post, I talked about the &lt;a href="http://blog.computationalcomplexity.org/2009/11/17x17-challenge-worth-28900-this-is-not.html"&gt;17x17 challenge&lt;/a&gt;, but did not provide any code.&amp;nbsp; Today, I will describe the code I use to represent a given grid.&amp;nbsp; Consider the following grid where &lt;b&gt;&lt;span style="color: red;"&gt;A&lt;/span&gt;&lt;/b&gt; and &lt;b&gt;&lt;span style="color: red;"&gt;b&lt;/span&gt;&lt;/b&gt; are used to represent two different colours:&lt;br /&gt;&lt;pre&gt;&lt;span style="color: red;"&gt;AbAb&lt;br /&gt;bAbA&lt;br /&gt;bbAA&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;&lt;span style="color: red;"&gt;bAAA&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;This grid has two rectangles, one of which is emphasized below&lt;br /&gt;&lt;pre&gt;....&lt;br /&gt;.&lt;span style="color: red;"&gt;A&lt;/span&gt;.&lt;span style="color: red;"&gt;A&lt;/span&gt;&lt;br /&gt;....&lt;br /&gt;.&lt;span style="color: red;"&gt;A&lt;/span&gt;.&lt;span style="color: red;"&gt;A&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;Finding such a rectangle in a small grid is fairly easy to do.&amp;nbsp; A naive, but very inefficient way to do this is to have a function like the following:&lt;br /&gt;&lt;pre class="py" name="code"&gt;def find_rect_candidates(row_1, row_2):&lt;br /&gt;   points_in_common = [0, 0, ...] # one per colour&lt;br /&gt;    for i, point in enumerate(row_1):&lt;br /&gt;       if row_1[i] == row_2[i]:&lt;br /&gt;          points_in_common[row_1[i]] += 1&lt;br /&gt;   return points_in_common&lt;/pre&gt;where row_x[i] is set to a given colour, represented by an integer (0, 1, 2, ...). For an NxN grid, the first loop is of order N.&amp;nbsp; Note that this does not tell us (yet) if we have found a rectangle; we still have to loop over &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;points_in_common&lt;/span&gt; and see if any entry is greater than 1.&lt;br /&gt;&lt;br /&gt;A better way to do the comparison, which does not grow with N, &lt;a href="http://bit-player.org/2009/the-17x17-challenge"&gt;is mentioned in this post&lt;/a&gt; and is based on the following observation:&amp;nbsp; for a given row, at a given point, a given colour is either present (True or 1) or not (False or 0). Thus, a given colour distribution on a single row can be represented as a string of 0s and 1s ... which can be thought of as the binary representation of an integer.&amp;nbsp; For example, the row containing the colour "A" in the pattern ".....A..A" can have this pattern represented by the number 9="1001".&amp;nbsp; Consider another row represented by the number 6="110". These two rows have no points in common (for this colour) and hence do not form a rectangle.&amp;nbsp; If we do a bitwise "and" for these two rows, i.e. 9&amp;amp;6 we will get zero.&amp;nbsp; This is achieved by a single operation instead of a series of comparisons.&lt;br /&gt;&lt;br /&gt;What happens if two rows have a single point in common (for a given colour) so that no rectangle is present?&amp;nbsp; For example, consider 9="1001" and 15="1110".&amp;nbsp; If we do a bitwise "and" we have&lt;br /&gt;&lt;br /&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;answer = 9 &amp;amp; 15 = 8 = "1000"&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;Taking the bitwise "and" of &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;answer&lt;/span&gt; and &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;answer-1&lt;/span&gt;, we get&lt;br /&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;answer &amp;amp; (answer-1) = 8 &amp;amp; 7 = "1000" &amp;amp; "111" = 0&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;If we have two points (1 bit) in common, it is easy to see that the bitwise comparison of &lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;answer&lt;/span&gt; &amp;amp; (&lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;answer-1&lt;/span&gt;) will not give zero.&lt;br /&gt;&lt;br /&gt;Going back to the function above, we could rewrite it instead as follows:&lt;br /&gt;&lt;br /&gt;&lt;pre class="py" name="code"&gt;def find_rect_candidates(row_1, row_2, colours):&lt;br /&gt;   points_in_common = [0, 0, ...] # one per colour&lt;br /&gt;      for c in colours:&lt;br /&gt;         points_in_common[c] = row_1[c] &amp;amp; row_2[c]&lt;br /&gt;   return points_in_common&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;where we still have to do the same (N-independent) processing of the return value as with the function above to determine if we do have rectangles.&lt;br /&gt;&lt;br /&gt;Now, without further ado, here is the basic code that I use to represent a grid, together with two utility functions.&lt;br /&gt;&lt;br /&gt;&lt;pre class="py" name="code"&gt;def rectangles_for_two_rows(row_1, row_2):&lt;br /&gt;    '''returns 0 if two rows (given colour, encoded as a bit string)&lt;br /&gt;        do not form a rectangle -&lt;br /&gt;        otherwise returns the points in common encoded as a bit string'''&lt;br /&gt;    intersect = row_1 &amp;amp; row_2&lt;br /&gt;    if not intersect:   # no point in common&lt;br /&gt;        return 0&lt;br /&gt;    # perhaps there is only one point in common of the form 00010000...&lt;br /&gt;    if not(intersect &amp;amp; (intersect-1)):&lt;br /&gt;        return 0&lt;br /&gt;    else:&lt;br /&gt;        return intersect&lt;br /&gt;&lt;br /&gt;def count_bits(x):&lt;br /&gt;    '''counts the number of bits&lt;br /&gt;    see http://stackoverflow.com/questions/407587/python-set-bits-count-popcount&lt;br /&gt;    for reference and other alternative'''&lt;br /&gt;    return bin(x).count('1')&lt;br /&gt;&lt;br /&gt;class AbstractGrid(object):&lt;br /&gt;    def __init__(self, nb_colours, max_rows, max_columns):&lt;br /&gt;        self.nb_colours = nb_colours&lt;br /&gt;        self.colours = list(range(nb_colours))&lt;br /&gt;        self.max_rows = max_rows&lt;br /&gt;        self.max_columns = max_columns&lt;br /&gt;        self.powers_of_two = [2**i for i in range(max_columns)]&lt;br /&gt;        self.grid = {}&lt;br /&gt;&lt;br /&gt;    def initialise(self):&lt;br /&gt;        '''initialise a grid according to some strategy'''&lt;br /&gt;        raise NotImplemented&lt;br /&gt;&lt;br /&gt;    def print_grid(self):&lt;br /&gt;        '''prints a representation of the grid.&lt;br /&gt;        Used for diagnostic only - no need to optimize further.'''&lt;br /&gt;        for row in self.grid:&lt;br /&gt;            row_rep = []&lt;br /&gt;            for column in range(self.max_columns-1, -1, -1):&lt;br /&gt;                for colour_ in self.colours:&lt;br /&gt;                    if self.powers_of_two[column] &amp;amp; self.grid[row][colour_]:&lt;br /&gt;                        row_rep.append(str(colour_))&lt;br /&gt;            print("".join(row_rep))&lt;br /&gt;&lt;br /&gt;    def identify_intersect_points(self):&lt;br /&gt;        '''identifies the dots that contribute to forming rectangles'''&lt;br /&gt;        # possibly could cut down the calculation time by only computing&lt;br /&gt;        # for colours that have changed...&lt;br /&gt;        nb_intersect = [0 for colour in self.colours]&lt;br /&gt;        intersect_info = []&lt;br /&gt;        for colour in self.colours:&lt;br /&gt;            for row in self.grid:&lt;br /&gt;                for other_row in range(row+1, self.max_rows):&lt;br /&gt;                    intersect = rectangles_for_two_rows(&lt;br /&gt;                                            self.grid[row][colour],&lt;br /&gt;                                            self.grid[other_row][colour])&lt;br /&gt;                    if intersect != 0:&lt;br /&gt;                        nb_intersect[colour] += count_bits(intersect)&lt;br /&gt;                        intersect_info.append([colour, row, other_row, intersect])&lt;br /&gt;        return nb_intersect, intersect_info&lt;/pre&gt;&lt;br /&gt;The actual code I use has a few additional methods introduced for convenience.&amp;nbsp; If anyone can find a better method to identify intersection points between rows (from which rectangles can be formed), I would be very interested to hear about it.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-5603167416470443360?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/5603167416470443360/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=5603167416470443360' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/5603167416470443360'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/5603167416470443360'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2009/12/17x17-challenge-setting-up-grid.html' title='The 17x17 challenge: setting up the grid'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-8849339951360655974</id><published>2009-12-25T00:55:00.002-04:00</published><updated>2009-12-25T01:20:13.461-04:00</updated><title type='text'>The 17x17 challenge</title><content type='html'>In a &lt;a href="http://blog.computationalcomplexity.org/2009/11/17x17-challenge-worth-28900-this-is-not.html"&gt;recent blog post&lt;/a&gt;, William Gasarch issued a challenge: find a 17x17 four-colour grid for which there are no rectangles with 4 corners of the same colour.&amp;nbsp; If you can do this, Gasarch will give you $289.&amp;nbsp; For a more detailed explanation, you can either read &lt;a href="http://blog.computationalcomplexity.org/2009/11/17x17-challenge-worth-28900-this-is-not.html"&gt;Gasarch's post&lt;/a&gt;, or &lt;a href="http://bit-player.org/2009/the-17x17-challenge"&gt;this post&lt;/a&gt; which contains a slightly friendlier explanation of the problem.&lt;br /&gt;&lt;br /&gt;This problem, which is fairly easy to state, is extremely hard to solve as the number of possible grid configurations is 4&lt;sup&gt;289&lt;/sup&gt; which is a number much too large to solve by random searches or by naive methods.&amp;nbsp; As can be seen from the comments of the posts mentioned above, many people have devoted a lot of cpu cycles in an attempt to find a solution, but with no success.&amp;nbsp; Like others, I have tried to write programs to solve it ... but with no luck.&amp;nbsp; In a future post, I may write about the various strategies I have implemented in Python in an attempt to solve this problem.&lt;br /&gt;&lt;br /&gt;Gasarch mentions that it is not known if a solution can be found for a 17x18 four-colour grid or even an 18x18 grid or a 17x19 one.&amp;nbsp; (Note that because of symmetry, an MxN solution can be rotated to give an NxM solution.)&amp;nbsp; &lt;strike&gt;While the number of configurations is large, the larger the grid is, I believe I have found a proof demonstrating that no solution can be found for a 17x18 grid.&lt;/strike&gt;&amp;nbsp; This &lt;strike&gt;proof &lt;/strike&gt;builds on the &lt;a href="http://www.cs.umd.edu/%7Egasarch/BLOGPAPERS/bethk.pdf"&gt;work of Elizabeth Kupin&lt;/a&gt; who has found the largest "colour class" i.e. the number of single-colour points on a 17x17 grid which is rectangle-free.&amp;nbsp; A generic solution found by Kupin is reproduced below:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_KpSgL5zKAvE/SzQ7wyXQhWI/AAAAAAAAACE/4HY-SyeB27c/s1600-h/grid.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/_KpSgL5zKAvE/SzQ7wyXQhWI/AAAAAAAAACE/4HY-SyeB27c/s640/grid.png" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;Consider a 17x18 grid (17 rows and 18 columns).&amp;nbsp; Such a grid has 306 points.&amp;nbsp; Using the pigeonhole principle, one can easily show that, for any colouring, at least one colour must cover 77 points.&amp;nbsp; (Indeed, the most symmetric colouring would be 77, 77, 76, 76.)&amp;nbsp; Also, if a 17x18 rectangle-free solution exist, it must contain a 17x17 rectangle-free subset, which we take to be the above solution (other solutions for the 17x17 grid can be derived from this one using interchanges of rows and/or columns).&lt;br /&gt;&lt;br /&gt;Let us attempt to add points in an additional column. First, we can try to add points in a row with 4 elements.&amp;nbsp; Without loss of generality, we can take this row to be the first one (row A).&amp;nbsp; Once we do this, we can not add any more points to row 3-17 without creating a rectangle.&amp;nbsp; The only row to which we can add a point is the second (row B) bringing the total number of points to 76 - one short of what we need.&lt;br /&gt;&lt;br /&gt;Perhaps we can first remove a point from the 17x17 solution and then add a new column.&amp;nbsp; There are three cases we must consider: 1) that of a point belonging to a row with 5 points and a column with 5 points; 2) that of a point belonging to a row with 5 points and a column with 4 points; 3) that of a point belonging to a row with 4 points and a column with 5 points.&lt;br /&gt;&lt;br /&gt;Case 1) Without loss of generality, let us move the point on row F in the first column to a new additional column, keeping the number of points at 74.&amp;nbsp; It is easy to show that the only rows to which we can then add an additional point without creating a rectangle are rows A, C, D, E. Once we add such a point (say on row A), we can no longer add a point on any of the remaining rows (C, D, E) without creating a rectangle.&lt;br /&gt;&lt;br /&gt;Case 2) Without loss of generality, let us move the left-most point on row Q to a new column.&amp;nbsp; The only row to which we can then add a point in this new column is row A, bringing the total to 75.&amp;nbsp; We can't add another point without creating a rectangle.&lt;br /&gt;&lt;br /&gt;Case 3) Again, without loss of generality, let us remove the top-left element (A) and move it to a new column added to the  above solution.&amp;nbsp; We can add one more point, bringing the total to 75,  to that new column (in row B) without creating a rectangle; any other  addition of a point on that new column will result in a rectangle.&lt;br /&gt;&lt;strike&gt;&lt;br /&gt;&lt;/strike&gt;&lt;br /&gt;&lt;strike&gt;This concludes the sketch of the proof.&amp;nbsp;&lt;/strike&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;UPDATE: &lt;/b&gt;&lt;i&gt;Instead of adding a column, we can add a row (R) with 3 points located in the 9th, 12th and 17th column, bringing the total to 77 points and keeping the grid rectangle-free.&lt;/i&gt;&amp;nbsp; &lt;i&gt;So the 17x18 case is still open... but I can't see how one could add a row with an extra 4 points to build a "single-colour" rectangle-free 17x19 grid.&amp;nbsp; (However, I won't venture again to say it is a proof until I have looked at all cases exhaustively&lt;/i&gt;.)&lt;br /&gt;&lt;br /&gt;Note that construction of solutions for a 17x17 grid such as those found by Kupin, even for a single colours, can possibly be used to restrict the search space for the more general problem and help find a solution.&amp;nbsp; Unfortunately, no one has been able to do this (yet).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-8849339951360655974?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/8849339951360655974/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=8849339951360655974' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/8849339951360655974'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/8849339951360655974'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2009/12/17x17-challenge.html' title='The 17x17 challenge'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_KpSgL5zKAvE/SzQ7wyXQhWI/AAAAAAAAACE/4HY-SyeB27c/s72-c/grid.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-5298331568194795572</id><published>2009-11-15T15:45:00.000-04:00</published><updated>2009-11-15T15:45:33.363-04:00</updated><title type='text'>Google Wave Fun</title><content type='html'>After a long time waiting, I finally got an Google Wave invitation this week - just as I was starting to look forward to a work slowdown so I could start programming again in the evenings.&amp;nbsp; The call of playing with a new toy was enough motivation to overcome inertia and spend a few hours, here and there in the evening, in order to create a new "gadget".&amp;nbsp; &lt;a href="http://code.google.com/apis/wave/extensions/gadgets/guide.html"&gt;Google Wave Gadgets&lt;/a&gt; are actually fairly easy to create.&amp;nbsp; The only downside (from my point of view) is that they have to be written using javascript instead of Python.&amp;nbsp; Since this blog is called "Only Python", you may wonder how a post about a Google Wave Gadget could possibly belong here.&amp;nbsp; Believe me, it does.&lt;br /&gt;&lt;br /&gt;A few years ago, in order to learn programming in Python, I created a "Karel the Robot" application in Python, to teach/learn Python, called &lt;a href="http://rur-ple.sourceforge.net/"&gt;rur-ple&lt;/a&gt;.&amp;nbsp; rur-ple was actually inspired by &lt;a href="http://gvr.sourceforge.net/"&gt;Guido van Robot&lt;/a&gt; (or GvR) which uses a Python like language.&lt;br /&gt;&lt;br /&gt;I thought it would be a good idea to have something like rur-ple as a wave gadget.&amp;nbsp; Rather than re-inventing the wheel, I contacted &lt;a href="http://www.carduner.net/home/index.html"&gt;Paul Carduner&lt;/a&gt;, who wrote &lt;a href="http://gvr.carduner.net/ui/index.html"&gt;gvr-online&lt;/a&gt;, and suggested that we could collaborate on a gadget - and he agreed.&amp;nbsp; The end goal is to have something like gvr-online as a gadget, but one which would recognize both the traditional GvR syntax OR the Python syntax used with rur-ple, as a choice for the end user.&lt;br /&gt;&lt;br /&gt;The idea of having something like gvr-online as a wave gadget is the following: a teacher can embed such a gadget in a wave, with a predefined world, and assign it as a problem to either one, or more students - who could collaborate in that wave. All wave participants, including the teacher, can thus contribute and help each other.&lt;br /&gt;&lt;br /&gt;Having had the idea, I wrote what was basically an empty shell of a gadget which Paul quickly connected to the existing code for gvr-online.&amp;nbsp; Some minor work remained for me to do to enable shared states and, apart from a minor UI bug, the prototype is now working.&amp;nbsp; What's left to do includes incorporation the Python syntax as an alternative to the traditional GvR syntax.&lt;br /&gt;&lt;br /&gt;For those that might be interested in such a gadget, I suggest you first check &lt;a href="http://gvr.carduner.net/ui/index.html"&gt;gvr-online&lt;/a&gt; itself to get a better idea of what it does.&amp;nbsp; Then, if you have a Google Wave account, you can embed the gadget in a wave using &lt;a href="http://py-fun.googlecode.com/svn/trunk/google_wave/test1.xml"&gt;this link&lt;/a&gt;.&amp;nbsp; Note that this is our "work in progress" link, that should not be expected to be a permanent one.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-5298331568194795572?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/5298331568194795572/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=5298331568194795572' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/5298331568194795572'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/5298331568194795572'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2009/11/google-wave-fun.html' title='Google Wave Fun'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-3646086272933088398</id><published>2009-09-13T17:21:00.000-03:00</published><updated>2009-09-13T17:21:55.095-03:00</updated><title type='text'>Live preview of reStructuredText to HTML conversion in your browser</title><content type='html'>I've implemented a new plugin for Crunchy which may be of interest to some: a reStructuredText editor with live preview inside the browser window.&amp;nbsp; Each time a key is pressed in the editor (html textarea), an ajax request is sent to the Python backend with the entire content of the editor.&amp;nbsp; This is processed by docutils with the result sent back to the browser as a full html page displayed inside the browser as illustrated below.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_KpSgL5zKAvE/Sq1R6ZJnf6I/AAAAAAAAAB8/DUY2xzSoESY/s1600-h/screen.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/_KpSgL5zKAvE/Sq1R6ZJnf6I/AAAAAAAAAB8/DUY2xzSoESY/s400/screen.png" /&gt;&lt;/a&gt;&lt;/div&gt;I expected this to be slow (make that &lt;b&gt;very slow&lt;/b&gt;) and for the first prototype I wrote, I only sent the ajax request when "enter" was pressed.&amp;nbsp; After playing with it for a while, I realized that there was no need for this.&lt;br /&gt;&lt;br /&gt;If you type fast enough, the previewer does not keep up for each key press, but this does not hinder in any way the speed at which you can enter text in the editor.&amp;nbsp; By the way, the editor (html textarea) can be converted into a true embedded editor which can save and load files, etc. - otherwise, it would not be a very useful tool for working with reStructuredText documents.&amp;nbsp; When doing so, the live preview is turned off.&amp;nbsp; I've also included the possibility to save the html file produced so that one does not have to invoke docutils separately.&amp;nbsp; And this also incorporates the two custom directives (crunchy and getpythonsource) used by Crunchy and crst2s5.&lt;br /&gt;&lt;br /&gt;This is included in the newest release (1.1.2) of Crunchy.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-3646086272933088398?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/3646086272933088398/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=3646086272933088398' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/3646086272933088398'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/3646086272933088398'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2009/09/live-preview-of-restructuredtext-to.html' title='Live preview of reStructuredText to HTML conversion in your browser'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_KpSgL5zKAvE/Sq1R6ZJnf6I/AAAAAAAAAB8/DUY2xzSoESY/s72-c/screen.png' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-2060177277870459682</id><published>2009-09-03T14:08:00.000-03:00</published><updated>2009-09-03T14:08:54.451-03:00</updated><title type='text'>Something I am working on...</title><content type='html'>Coming soon ... with "live" preview ...&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_KpSgL5zKAvE/Sp_3zaXRrtI/AAAAAAAAAB0/XeBkViE1QFk/s1600-h/rst_test.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/_KpSgL5zKAvE/Sp_3zaXRrtI/AAAAAAAAAB0/XeBkViE1QFk/s400/rst_test.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-2060177277870459682?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/2060177277870459682/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=2060177277870459682' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/2060177277870459682'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/2060177277870459682'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2009/09/something-i-am-working-on.html' title='Something I am working on...'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_KpSgL5zKAvE/Sp_3zaXRrtI/AAAAAAAAAB0/XeBkViE1QFk/s72-c/rst_test.png' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-4869054523060701949</id><published>2009-09-02T20:15:00.000-03:00</published><updated>2009-09-02T20:15:48.802-03:00</updated><title type='text'>Upcoming Pycon and crst2s5</title><content type='html'>In a few months, many pythonistas will be converging to Atlanta to take part in the &lt;a href="http://us.pycon.org/2010/about/"&gt;next Pycon&lt;/a&gt;.&amp;nbsp; I'm hoping to be able to attend it as I have been really inspired by the many people I met in two previous conferences.&amp;nbsp; While I most likely won't be presenting anything this time, I thought I would try to contribute to some presentations in an indirect way.&lt;br /&gt;&lt;br /&gt;A while ago, Ned Batchelder &lt;a href="http://nedbatchelder.com/blog/200811/presentation_tools.html"&gt;wrote about presentation tools&lt;/a&gt; - more specifically about how he found all of them lacking.&amp;nbsp; In the comments section, many people made various suggestions - including one suggestion by Steve Holden which read as follows: &lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;i&gt;Crunchy? &lt;a href="http://code.google.com/p/crunchy/" rel="nofollow"&gt;http://code.google.com/p/crunchy/&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;HTML plus live Python - what could be better?   &lt;/i&gt;&lt;/blockquote&gt;While I, of course, approve of any positive support of Crunchy, I had to admit to myself (and mention in a subsequent comment) that Crunchy was really not up to the task - at least not yet.&lt;br /&gt;&lt;br /&gt;In his post, Ned addresses some of the weaknesses of the tools he looked at.&amp;nbsp; In particular, he mentioned S5 about which he wrote:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;i&gt;&lt;a class="offsite" href="http://meyerweb.com/eric/tools/s5/"&gt;S5&lt;/a&gt; (A Simple Standards-Based Slide Show System) is all HTML, CSS, and Javascript, runs in the browser, and was created by Eric Meyer, a very nice pedigree.  Remarkably, the thing I like least about it is that when I display the slides in a full-screen Firefox, they look horrible. The text is either too small or too large, and the line-spacing too tight, while the slide title overlaps the text in the wrong way, exposing some of the structure of the divs.&lt;/i&gt;&lt;br /&gt;&lt;i&gt;I would have hoped that a CSS-based slideshow by the king of CSS would be a shining example of how information could be cleanly authored and then sparklingly displayed.  S5 seems to miss this mark, especially since there don't seem to be many themes available for it, another surprise given how CSS should have made it accessible to lots of designers.  Also, although (or perhaps because) the format is native to the web, it's not possible to get the slides as illustrations.&lt;/i&gt;&lt;/blockquote&gt;Still, S5 is used for quite a few presentations by pythonistas thank to &lt;a href="http://docutils.sourceforge.net/docs/user/slide-shows.s5.html"&gt;rst2s5&lt;/a&gt;, the docutils script that transform reStructuredText files into S5 presentations.&amp;nbsp; If I can summarize, rst2s5 strengths are as follow:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;uses reStructuredText as source - easier to write than html&lt;/li&gt;&lt;/ul&gt;&lt;ul&gt;&lt;li&gt;produce html files - thus easy to post and share -see link above.&lt;/li&gt;&lt;/ul&gt;&lt;ul&gt;&lt;li&gt;use well known S5 slides (version 1.1 designed by Eric Meyer) to switch between "full paper"/slides.&amp;nbsp; It is possible to include extra notes in the "full paper".&lt;/li&gt;&lt;/ul&gt;Some of the weaknesses are the following:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;From rst2s5's own documentation, it is impossible to have:&lt;/li&gt;&lt;/ul&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; - speaker's notes on the laptop screen&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; - slides on the projector&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; - two views stay in sync&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; - presentation controlled from either display&lt;br /&gt;&lt;ul&gt;&lt;li&gt;like most (all?) slide-based presentation, content on one slide is limited to a given number of lines ... which makes it ackward to show long-ish code samples.&lt;/li&gt;&lt;li&gt;as Ned Batchelder alludes to, the automatic scaling done by S5 often yields unsatisfactory results in terms of line spacing, etc.&lt;/li&gt;&lt;li&gt;Finally, no interactive Python code in presentation ;-)&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;rst2s5 is based on S5 version 1.1.&amp;nbsp; A new version (1.2 alpha) is available which addresses some of the shortcomings of version 1.1 with the addition of speaker's notes which are in a separate window (e.g. on the laptop screen) while the slides themselves are in a different window (which can be on the projector) with the two synchronized.&lt;br /&gt;&lt;br /&gt;I have decided to hack at the source (I don't like javascript...) and adapt S5 1.2 in ways which address some of Ned Batchelder's criticisms about the line spacing, etc, by disabling the automatic font scaling.&amp;nbsp; I incorporated it in a new program called crst2s5 which can be thought of as a Crunchy-extension to rst2s5.&amp;nbsp; crst2s5 on its own can produce standalone presentations (like rst2s5).&amp;nbsp; I consider it to be an improvement to rst2s5 ... time will tell if others share my opinion.&lt;br /&gt;&lt;br /&gt;However, where crst2s5 can yield really interesting results is when you view the html output with Crunchy.&amp;nbsp; For now (version 1.1), when you do so, the automatic synchronization of speaker notes and slides is disabled - this will be addressed hopefully in the near future.&amp;nbsp;&amp;nbsp; You can either try it by &lt;a href="http://code.google.com/p/crunchy/"&gt;downloading the code&lt;/a&gt; or have a look at this sneak preview which I put together in one quick take as an experiment: &lt;br /&gt;&lt;br /&gt;&lt;script src="http://showmedo.com/static/javascript/swfobject.js" type="text/javascript"&gt;&lt;/script&gt; &lt;br /&gt;&lt;br /&gt;&lt;div id="player"&gt;&lt;a href="http://www.macromedia.com/go/getflashplayer"&gt;Get the Flash Player&lt;/a&gt; to see this movie.&lt;/div&gt;&lt;script type="text/javascript"&gt;        var so = new SWFObject('http://showmedo.com/static/flv/flvplayer.swf','mpl','636','496','7');        so.addParam("allowfullscreen","true");        so.addVariable("enablejs","true");    so.addVariable("file","http://videos1.showmedo.com/ShowMeDos/1430040.flv");    so.addVariable("image","http://images.showmedo.com/static/screenshots/1430040.jpg");    so.write('player');    &lt;/script&gt;&lt;br /&gt;&lt;br /&gt;Original location: '&lt;a href="http://www.showmedo.com/videos/video?name=1430040&amp;amp;fromSeriesID=143"&gt;Crunchy&lt;/a&gt;' &lt;br /&gt;at ShowMeDo from the &lt;a href="http://www.showmedo.com/videos/python"&gt;Python&lt;/a&gt; category.&lt;br /&gt;&lt;br /&gt;Now, if only I had better graphical skills to design good looking slides...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-4869054523060701949?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/4869054523060701949/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=4869054523060701949' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/4869054523060701949'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/4869054523060701949'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2009/09/upcoming-pycon-and-crst2s5.html' title='Upcoming Pycon and crst2s5'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-7922609451322224543</id><published>2009-08-23T21:56:00.003-03:00</published><updated>2009-08-23T22:40:41.039-03:00</updated><title type='text'>New plugin for Crunchy ... and bug fixes</title><content type='html'>&lt;a href="http://code.google.com/p/crunchy"&gt;Crunchy&lt;/a&gt; has a new plugin: getsource. What it does is enable a tutorial writer to embed a "link" to a python module inside an html file, or a class within that module, or a function or method, and have the source code being extracted by the &lt;a href="http://docs.python.org/library/inspect.html"&gt;inspect module&lt;/a&gt; and inserted within the html page. &lt;br /&gt;&lt;br /&gt;Crunchy being Crunchy, it can also embed an interpreter or an editor right below the code source so that a user can interact with it.&lt;br /&gt;&lt;br /&gt;And since not everyone likes to write documentation using straight html, a custom docutils directive is supported so that it works from reStructuredText files too.&lt;br /&gt;&lt;br /&gt;The docutils directive looks as follows:&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 102, 0);"&gt;..getsource:: relative/path/to/module[.function] [linenumber] [editor or interpreter]&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;with a similar syntax for html files.&lt;br /&gt;&lt;br /&gt;This plugin (which still has some minor bugs) is included in release 1.0.1 of Crunchy, which contains other minor bug fixes (as compared with release 1.0).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-7922609451322224543?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/7922609451322224543/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=7922609451322224543' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/7922609451322224543'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/7922609451322224543'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2009/08/new-plugin-for-crunchy-and-bug-fixes.html' title='New plugin for Crunchy ... and bug fixes'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-2495313866006006529</id><published>2009-08-23T01:24:00.002-03:00</published><updated>2009-08-23T01:31:17.322-03:00</updated><title type='text'>Crunchy 1.0 released!!</title><content type='html'>&lt;a href="http://code.google.com/p/crunchy/"&gt;Crunchy 1.0&lt;/a&gt; has finally been released. :-)&lt;br /&gt;&lt;br /&gt;Crunchy 1.0 is compatible with Python 2.4, 2.5, 2.6 ... and 3.1.  It is also compatible with Jython 2.5 modulo some bugs when trying to work with examples containing unicode strings.&lt;br /&gt;&lt;br /&gt;Crunchy, for those that are not familiar with it, is a one-of-a-kind application.  It is an application designed to transforms otherwise static html Python tutorials into interactive session viewed within a browser.   Currently Crunchy has only been fully tested with Firefox.  Crunchy should work with all operating systems - it has been tested fairly extensively on Linux, Windows and Mac OS.&lt;br /&gt;&lt;br /&gt;In more details, here's what Crunchy does:&lt;br /&gt;&lt;br /&gt;1. It loads an existing html file (or reStructuredText file) containing some Python code; this file can reside locally, or anywhere on the web.&lt;br /&gt;2. It removes any existing javascript code &amp;amp; embedded interactive elements (applets, flash apps, etc.).&lt;br /&gt;3. It further transforms the file by inserting some extra elements, including some custom javascript code.&lt;br /&gt;4. It sends the transformed file to your browser of choice (read: Firefox).&lt;br /&gt;5. It establishes a communication between the browser and the Crunchy back end and wait for user interaction.&lt;br /&gt;&lt;br /&gt;Crunchy can embed Python interpreters and code editors (and more!) within an html page, enabling a user to enter (or edit) some Python code inside a browser window, send it to the Crunchy back end for execution, and observe the result back in the browser window.&lt;br /&gt;&lt;br /&gt;In a sense, Crunchy is a Python programming environment embedded within a browser. However, it does not require a custom format for interactivity: for html files, as long as the Python code is included in a pre-formatted element (&amp;lt;pre&amp;gt;), Crunchy will recognize it, style it, and include the appropriate interactive element.&lt;br /&gt;&lt;br /&gt;Crunchy comes with a fairly complete tutorial and supporting documentation.  It is highly configurable.&lt;br /&gt;&lt;br /&gt;Release 1.0 is NOT the end of the road for Crunchy.  Future plans include support for interactivity with languages other than Python.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-2495313866006006529?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/2495313866006006529/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=2495313866006006529' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/2495313866006006529'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/2495313866006006529'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2009/08/crunchy-10-released.html' title='Crunchy 1.0 released!!'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-1278543634952354706</id><published>2009-04-16T17:41:00.002-03:00</published><updated>2009-04-16T17:51:29.047-03:00</updated><title type='text'>Python textbooks wanted</title><content type='html'>Python textbooks wanted(1).  Actually, what I'm mostly interested in are links to textbooks and other resources that would be useful to educators either teaching Python as a programming language or other courses where Python feature prominently.  The reason behind this request is that I volunteered to be responsible to take care of the &lt;a href="http://www.python.org/community/sigs/current/edu-sig/"&gt;edu-sig page&lt;/a&gt; on the Python site.  Through no one's fault in particular, the &lt;a href="http://www.python.org/community/sigs/current/edu-sig/old_page/"&gt;old page (2)&lt;/a&gt; had gone stale and only one textbook was listed.  We're now up to six and I am sure I have not included them all.  By ensuring that up to date and complete information is available on that page, we can facilitate the adoption of Python as a language taught in High Schools, Colleges and Universities.&lt;br /&gt;&lt;br /&gt;So, if you know of any useful information that should be added to the edu-sig page, please let me know.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:78%;"&gt;(1)&lt;/span&gt; &lt;span style="font-size:78%;"&gt;Actually, this is not exactly correct ... but if you want to send me some for reviews, I won't say no. ;-)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:78%;"&gt;(2) The page linked here will eventually disappear...&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-1278543634952354706?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/1278543634952354706/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=1278543634952354706' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/1278543634952354706'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/1278543634952354706'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2009/04/python-textbooks-wanted.html' title='Python textbooks wanted'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-4106206283082921159</id><published>2009-04-10T14:47:00.003-03:00</published><updated>2009-04-10T14:59:59.888-03:00</updated><title type='text'>Learning paths at ShowMeDo</title><content type='html'>&lt;a href="http://showmedo.com"&gt;ShowMeDo&lt;/a&gt; is a great site to learn about Python (and a few other subjects) by watching screncasts.  One of my favourite videos is &lt;a href="http://showmedo.com/videos/series?name=v7kABKL6R"&gt;Learn Django: Create a Wiki in 20 minutes&lt;/a&gt;, which taught me the basic concepts so that I was later able to adapt and play around with Google App Engine.  Many authors on ShowMeDo have multiple screencasts &lt;span style="font-size:78%;"&gt;(I made a few on RUR-PLE and Crunchy a while ago)&lt;/span&gt;, some of which &lt;span style="font-size:78%;"&gt;(not mine)&lt;/span&gt; are very professional looking.  Such series of videos give the viewpoint of a given author, introducing some concepts in a logical sequence.&lt;br /&gt;&lt;br /&gt;The newest addition to the ShowMeDo site is called &lt;a href="http://showmedo.com/learningpaths/"&gt;Learning Paths&lt;/a&gt;.  The idea of Learning Paths is to improve upon the existing series by including videos from multiple authors into a coherent sequence.  While this concept is very much in its infancy, it promises to become a great addition to what is already a fantastic site.&lt;br /&gt;&lt;br /&gt;In case anyone were wondering: I have no financial interest whatsover in ShowMeDo; I am just a satisfied user who considers this site a very good resource, well worth exploring.  &lt;span style="font-size:78%;"&gt;If only I had more time...&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-4106206283082921159?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/4106206283082921159/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=4106206283082921159' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/4106206283082921159'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/4106206283082921159'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2009/04/learning-paths-at-showmedo.html' title='Learning paths at ShowMeDo'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-7836959730019259885</id><published>2009-01-08T22:32:00.004-04:00</published><updated>2009-01-09T07:01:53.353-04:00</updated><title type='text'>Testing code highlighting</title><content type='html'>&lt;span style="font-weight: bold;"&gt;Update:&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;In order to have the styling work, I had to include by hand the styling information (keyword and string) from &lt;a href="http://syntaxhighlighter.googlecode.com/svn-history/r57/trunk/Styles/SyntaxHighlighter.css"&gt;this style sheet&lt;/a&gt; - and adapt as desired.&lt;span style="font-weight: bold;"&gt;&lt;br /&gt;&lt;br /&gt;- - - - - - -&lt;br /&gt;&lt;/span&gt;This is a test of Python code highlighting on Blogger following &lt;a href="http://itscommonsensestupid.blogspot.com/2009/01/code-syntax-highlighter-on-blogger.html"&gt;these instructions&lt;/a&gt;.&lt;br /&gt;&lt;pre name="code" class="py"&gt;print "Hello World!"&lt;br /&gt;&lt;br /&gt;# And this is a comment&lt;br /&gt;&lt;/pre&gt;End of test.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-7836959730019259885?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/7836959730019259885/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=7836959730019259885' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/7836959730019259885'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/7836959730019259885'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2009/01/testing-code-highlighting.html' title='Testing code highlighting'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-5603343130115260470</id><published>2008-12-20T23:44:00.007-04:00</published><updated>2008-12-21T16:52:47.240-04:00</updated><title type='text'>Plugins - part 6: setuptools based approach</title><content type='html'>&lt;a href="http://peak.telecommunity.com/DevCenter/setuptools"&gt;setuptools&lt;/a&gt; is collection of enhancements to the standard Python distutils module.  While it is not included in the Python standard library, chances are that it is already installed on your system if you have installed some additional Python libraries since it is so widely used to install Python packages ("eggs") via easy_install.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://peak.telecommunity.com/DevCenter/setuptools"&gt;setuptools&lt;/a&gt; is also used in many applications as a handler for plugins.   Many such applications include tutorials for creating new plugins using setuptools.   For a somewhat general introduction to using setuptools to create plugin based applications, I suggest you have a look at the &lt;a href="http://lucumr.pocoo.org/blogarchive/setuptools-plugins"&gt;two&lt;/a&gt; &lt;a href="http://base-art.net/Articles/64/"&gt;tutorials&lt;/a&gt; from which this one is inspired.&lt;br /&gt;&lt;br /&gt;Our starting point for this tutorial is essentially the same as in &lt;a href="http://aroberge.blogspot.com/2008/12/plugins-part-3-simple-class-based.html"&gt;the third one in this series&lt;/a&gt;.  We start with the following files:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;root_directory/&lt;br /&gt;    calculator.py&lt;br /&gt;    &lt;span style="font-weight: bold; color: rgb(51, 51, 255);"&gt;setup.py&lt;/span&gt;&lt;br /&gt;    plugins/&lt;br /&gt;        __init__.py&lt;br /&gt;        base.py&lt;br /&gt;        op_1.py&lt;br /&gt;        op_2.py&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;where we have one new file, setup.py.  This file is a special file for setuptools.  Its content is as follows:&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:130%;"&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;''' run with python setup.py develop '''&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;from setuptools import setup, find_packages&lt;br /&gt;&lt;br /&gt;setup(&lt;br /&gt;    name="Calculator_s_tools",&lt;br /&gt;    version="1.0",&lt;br /&gt;    packages=['plugins'],&lt;br /&gt;    entry_points=&lt;span style="color: rgb(0, 153, 0);"&gt;"""&lt;/span&gt;&lt;br /&gt;        &lt;span style="color: rgb(0, 153, 0);"&gt;[plugin_tutorial.s_tools]&lt;/span&gt;&lt;br /&gt;        &lt;span style="color: rgb(0, 153, 0);"&gt;add = plugins.op_1:operator_add_token&lt;/span&gt;&lt;br /&gt;        &lt;span style="color: rgb(0, 153, 0);"&gt;sub = plugins.op_1:operator_sub_token&lt;/span&gt;&lt;br /&gt;        &lt;span style="color: rgb(0, 153, 0);"&gt;mul = plugins.op_1:operator_mul_token&lt;/span&gt;&lt;br /&gt;        &lt;span style="color: rgb(0, 153, 0);"&gt;div = plugins.op_1:operator_div_token&lt;/span&gt;&lt;br /&gt;        &lt;span style="color: rgb(0, 153, 0);"&gt;pow = plugins.op_2:operator_pow_token"""&lt;br /&gt;&lt;/span&gt;       )&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;The key concept for setuptools is that of "entry_points".  We define some entry points, with a name "plugin_tutorial.s_tools" chosen as unique to our application.  Within this entrypoint we indicate which classes should be imported.  This method effectively replace our custom method for finding and loading plugins.  However, if you remember from previous tutorials, the  way the application was designed originally (all within a single file) resulted in a "wart", where we had to create a link to a function ("expression") in each plugin module.  Since setuptools will import the classes for us, we have no way to tell it how to fix that "wart" - we have to find another way.  The method we chose was to create a different Plugin base class, one that implements the Borg idiom, so that each instance shares common attributes.  Here's the new "base.py":&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;import os&lt;br /&gt;import sys&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;import pkg_resources&lt;/span&gt;  &lt;span style="color: rgb(0, 153, 0);"&gt;# setuptools specific&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;OPERATORS = {}&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;ENTRYPOINT = 'plugin_tutorial.s_tools'&lt;/span&gt;  &lt;span style="color: rgb(0, 153, 0);"&gt;# same name as in setup.py&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;class Plugin(object):&lt;/span&gt;&lt;br /&gt;    &lt;span style="color: rgb(0, 153, 0);"&gt;'''A Borg class.'''&lt;/span&gt;&lt;br /&gt;    _shared_states = {}&lt;br /&gt;    &lt;span style="font-weight: bold;"&gt;def __init__(self):&lt;/span&gt;&lt;br /&gt;         self.__dict__ = self._shared_states&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;def init_plugins(expression):&lt;/span&gt;&lt;br /&gt;    &lt;span style="color: rgb(0, 153, 0);"&gt;'''simple plugin initializer'''&lt;/span&gt;&lt;br /&gt;    Plugin().expression = expression  &lt;span style="color: rgb(0, 153, 0);"&gt;# fixing the wart&lt;/span&gt;&lt;br /&gt;    load_plugins()&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;def load_plugins():&lt;/span&gt;&lt;br /&gt;    &lt;span style="color: rgb(0, 153, 0);"&gt;'''setuptools based plugin loader'''&lt;/span&gt;&lt;br /&gt;    for entrypoint in pkg_resources.iter_entry_points(&lt;span style="color: rgb(51, 51, 255);"&gt;ENTRYPOINT&lt;/span&gt;):&lt;br /&gt;        plugin_class = entrypoint.load()&lt;br /&gt;        OPERATORS[plugin_class.symbol] = plugin_class&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;The actual plugin files are slightly modified to derive from the new Plugin class; for example, op_2.py contains the following:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;from plugins.base import Plugin&lt;br /&gt;&lt;br /&gt;class operator_pow_token(Plugin):&lt;br /&gt;    symbol = '**'&lt;br /&gt;    lbp = 30&lt;br /&gt;    def led(self, left):&lt;br /&gt;        return left ** &lt;span style="font-weight: bold; color: rgb(51, 51, 255);"&gt;self&lt;/span&gt;.expression(30-1)&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;where "expression" is now a class variable obtained from Plugin.&lt;br /&gt;&lt;br /&gt;The code involved to make the setuptools approach is approximately the same level of complexity as the class-based plugin system &lt;a href="http://aroberge.blogspot.com/2008/12/plugins-part-3-simple-class-based.html"&gt;covered previously&lt;/a&gt;.  The advantages of using the setuptools approach are as follows:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Since it is a widely used tool, many people know how to use it properly.&lt;/li&gt;&lt;li&gt;It is possible to package plugins as eggs to be uploaded to a repository.&lt;/li&gt;&lt;li&gt;It is possible to keep track of dependencies in a fairly detailed way (e.g. module X version Y required).&lt;br /&gt;&lt;/li&gt;&lt;/ol&gt;By comparison, it suffers from the following disadvantages:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Some information about plugin location (entry_points name) is duplicated, appearing in both setup.py and base.py (in our example).&lt;/li&gt;&lt;li&gt;Automatic plugin discovery without editing of a file (setup.py) is not possible, unlike the cases we covered before.   &lt;strike&gt;Because of this, dynamic loading of "external" plugins while the application is already running may be problematic to achieve.  (I am not familiar enough with setuptools to determine if it is feasible or not.)&lt;/strike&gt; See the first comment par Phillip J. Eby on how to achieve this.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;A preliminary step ("python setup.py develop") is required to generate entrypoints  information.&lt;/li&gt;&lt;li&gt;A number of additional files are created by the previous step, "cluttering" slightly the file structure by adding an extra directory with a few files.&lt;br /&gt;&lt;/li&gt;&lt;/ol&gt;That being said, the differences between the two approaches are relatively minor when everything is taken into account.  Choosing one approach over the other is a matter of individual taste - at least for simple applications such as the one we considered.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-5603343130115260470?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/5603343130115260470/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=5603343130115260470' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/5603343130115260470'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/5603343130115260470'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/12/plugins-part-6-setuptools-based.html' title='Plugins - part 6: setuptools based approach'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-464009103972683147</id><published>2008-12-20T15:18:00.005-04:00</published><updated>2008-12-20T18:14:39.686-04:00</updated><title type='text'>Plugins - part 5: Activation and Deactivation</title><content type='html'>&lt;span style="font-size:78%;"&gt;(Note: the code indentation may appear to be wrong due to some blogspot's quirks...)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;While plugins are a great way to extend the functionality of an application, sometimes it makes sense to limit the number of available features, based on a user's preference.  For example, &lt;a href="http://projects.gnome.org/gedit/"&gt;gedit&lt;/a&gt;, the official text editor of the Gnome environment, offers the possibility to &lt;a href="http://live.gnome.org/Gedit/PythonPluginHowTo"&gt;activate or deactivate a plugin&lt;/a&gt;.&lt;br /&gt;&lt;img src="http://live.gnome.org/Gedit/PythonPluginHowTo?action=AttachFile&amp;amp;do=get&amp;amp;target=examplepy.png" alt="[link to image of activated plugins for gedit]" /&gt;&lt;br /&gt;In this post, using the class-based plugin approach, I will explain how to add the possibility to activate or deactivate a given plugin.  Furthermore, I will show how to use this capability to dynamically load new plugins.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;font-size:130%;" &gt;Starting from the beginning...&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Our starting point will be the following modified core application (calculator.py):&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:130%;"&gt;import re&lt;br /&gt;&lt;br /&gt;from plugins.base import OPERATORS, init_plugins&lt;span style="color: rgb(51, 51, 255);"&gt;, activate, deactivate&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;class literal_token(object):&lt;br /&gt; def __init__(self, value):&lt;br /&gt;     self.value = value&lt;br /&gt; def nud(self):&lt;br /&gt;     return self.value&lt;br /&gt;&lt;br /&gt;class end_token(object):&lt;br /&gt;lbp = 0&lt;br /&gt;&lt;br /&gt;def tokenize(program):&lt;br /&gt; for number, operator in re.findall("\s*(?:(\d+)|(\*\*|.))", program):&lt;br /&gt;    if number:&lt;br /&gt;        yield literal_token(int(number))&lt;br /&gt;    elif operator in OPERATORS:&lt;br /&gt;        yield OPERATORS[operator]()&lt;br /&gt;    else:&lt;br /&gt;        raise &lt;span style="color: rgb(204, 0, 0);"&gt;SyntaxError&lt;/span&gt;("unknown operator: %r" % operator)&lt;br /&gt; yield end_token()&lt;br /&gt;&lt;br /&gt;def expression(rbp=0):&lt;br /&gt; global token&lt;br /&gt; t = token&lt;br /&gt; token = next()&lt;br /&gt; left = t.nud()&lt;br /&gt; while rbp &amp;lt; token.lbp:&lt;br /&gt;     t = token&lt;br /&gt;     token = next()&lt;br /&gt;     left = t.led(left)&lt;br /&gt; return left&lt;br /&gt;&lt;br /&gt;def calculate(program):&lt;br /&gt; global token, next&lt;br /&gt; next = tokenize(program).next&lt;br /&gt; token = next()&lt;br /&gt; return expression()&lt;br /&gt;&lt;br /&gt;if __name__ == "__main__":&lt;br /&gt;init_plugins(expression)&lt;br /&gt;assert calculate("+1") == 1&lt;br /&gt;assert calculate("-1") == -1&lt;br /&gt;assert calculate("10") == 10&lt;br /&gt;assert calculate("1+2") == 3&lt;br /&gt;assert calculate("1+2+3") == 6&lt;br /&gt;assert calculate("1+2-3") == 0&lt;br /&gt;assert calculate("1+2*3") == 7&lt;br /&gt;assert calculate("1*2+3") == 5&lt;br /&gt;assert calculate("6*2/3") == 4&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;    # "**" has not been activated at the start in base.py&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;    try:&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;        assert calculate("2**3") == 8&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;    except &lt;span style="color: rgb(204, 0, 0);"&gt;SyntaxError&lt;/span&gt;:&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;        print "Correcting error..."&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;        &lt;span style="color: rgb(51, 51, 255);"&gt;activate&lt;/span&gt;("**")&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;    assert calculate("2*2**3") == 16&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(204, 0, 0);"&gt;   &lt;span style="color: rgb(51, 51, 255);"&gt; deactivate&lt;/span&gt;&lt;span style="color: rgb(51, 204, 0);"&gt;('+')&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 204, 0);"&gt;    try:&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 204, 0);"&gt;        assert calculate("1+2") == 3&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 204, 0);"&gt;    except &lt;span style="color: rgb(204, 0, 0);"&gt;SyntaxError&lt;/span&gt;:&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 204, 0);"&gt;        &lt;span style="color: rgb(51, 51, 255);"&gt;activate&lt;/span&gt;('+')&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 204, 0);"&gt;    assert calculate("1+2") == 3&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;print "Done!"&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The new features are indicated by different colours.   In blue, we have two new functions imported to either activate or deactivate a given plugin.  When the application is started, exponentiation is disabled - this can only be seen by looking at the modified version of base.py.  When a disabled plugin is called, a SyntaxError already present in the old version) is raised and we activate the plugin.&lt;br /&gt;&lt;br /&gt;To make this possible, we need to modify base.py.   Before showing the new version, here's the result of running the above code:&lt;br /&gt;&lt;pre&gt;&lt;span style="font-weight: bold;font-size:130%;" &gt;Activating +&lt;br /&gt;Activating -&lt;br /&gt;Activating *&lt;br /&gt;Activating /&lt;br /&gt;Correcting error...&lt;br /&gt;Activating **&lt;br /&gt;Deactivating +&lt;br /&gt;Activating +&lt;br /&gt;Done!&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And here's the new version of base.py:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;import os&lt;br /&gt;import sys&lt;br /&gt;&lt;br /&gt;OPERATORS = {}&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;# We simulate a configuration file that would be based on a user's preference&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;# as to which plugin should be activated by default&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;# We will leave one symbol "**" out of the list as a test.&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;preferences = ['+', '-', '*', '/']&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;# We also keep track of all available plugins, activated or not&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;all_plugins = {}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;class Plugin(object):&lt;/span&gt;&lt;br /&gt;'''base class for all plugins'''&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 0, 153); font-weight: bold;"&gt;    def activate(self):&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;        '''activate a given plugin'''&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;        if self.symbol not in OPERATORS:&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;            print "Activating %s" % self.symbol&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;            OPERATORS[self.symbol] = self.__class__&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;        if self.symbol not in all_plugins:&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;            all_plugins[self.symbol] = self.__class__&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 0, 153); font-weight: bold;"&gt;    def deactivate(self):&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;        '''deactivate a given plugin'''&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;        print "Deactivating %s" % self.symbol&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;        if self.symbol in OPERATORS:&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;            del OPERATORS[self.symbol]&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 0, 153); font-weight: bold;"&gt;def activate(symbol):&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;    '''activate a given plugin based on its symbol'''&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;    if symbol in OPERATORS:&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;        return&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;    all_plugins[symbol]().activate()&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 0, 153); font-weight: bold;"&gt;def deactivate(symbol):&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;    '''deactivate a given plugin, based on its symbol'''&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;    if symbol not in OPERATORS:&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;        return&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;    all_plugins[symbol]().deactivate()&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;def init_plugins(expression):&lt;br /&gt;'''simple plugin initializer&lt;br /&gt;'''&lt;br /&gt;find_plugins(expression)&lt;br /&gt;register_plugins()&lt;br /&gt;&lt;br /&gt;def find_plugins(expression):&lt;br /&gt;'''find all files in the plugin directory and imports them'''&lt;br /&gt;plugin_dir = os.path.dirname(os.path.realpath(__file__))&lt;br /&gt;plugin_files = [x[:-3] for x in os.listdir(plugin_dir) if x.endswith(".py")]&lt;br /&gt;sys.path.insert(0, plugin_dir)&lt;br /&gt;for plugin in plugin_files:&lt;br /&gt; mod = __import__(plugin)&lt;br /&gt; mod.expression = expression&lt;br /&gt;&lt;br /&gt;def register_plugins():&lt;br /&gt;'''Register all class based plugins.&lt;br /&gt;&lt;br /&gt;Uses the fact that a class knows about all of its subclasses&lt;br /&gt;to automatically initialize the relevant plugins&lt;br /&gt;'''&lt;br /&gt;for plugin in Plugin.__subclasses__():&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;        # only register plugins according to user's preferences&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;        if plugin.symbol in preferences:&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;            plugin().activate()&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;        else:   &lt;span style="color: rgb(0, 153, 0);"&gt;# record its existence&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;            all_plugins[plugin.symbol] = plugin&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;Changes from the old version are indicated in blue (with corresponding comments in green).  Note that we did not change a single line of code for the actual plugins!  We did use the same names (activate and deactivate) both for a function and a class method.  This should probably be avoided in a larger application.  In this example, the code is short enough that it should not create too much confusion.  In a real application we would also give the possibility of changing the user's preferences, storing the information in some configuration file.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;&lt;span style="font-size:130%;"&gt;Dynamic activation&lt;/span&gt;&lt;/span&gt;&lt;span style="font-size:130%;"&gt;&lt;span style="font-size:100%;"&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:100%;"&gt;Now that we now how to activate and deactivate a plugin, it might be useful to consider dynamic activation of an external plugin, not located in the normal plugins directory.  For example, consider the following plugin (located in op_3.py):&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;from plugins.base import Plugin&lt;br /&gt;&lt;br /&gt;class operator_mod_token(Plugin):&lt;br /&gt;symbol = '%'&lt;br /&gt;lbp = 10&lt;br /&gt;def nud(self):&lt;br /&gt;   return expression(100)&lt;br /&gt;def led(self, left):&lt;br /&gt;   return left % expression(10)&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;This file is located in subdirectory "external" which is at the same level as "plugins" in our &lt;a href="http://code.google.com/p/py-fun/source/browse/#svn/trunk/plugin-work/class_based_2"&gt;sample code&lt;/a&gt;.  To invoke this plugin from our base application, we need to add the following code to calculator.py:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;if __name__ == "__main__":&lt;br /&gt;#...&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;    # Simulating dynamic external plugin initialization&lt;/span&gt;&lt;br /&gt;external_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)),&lt;br /&gt;                          'external')&lt;br /&gt;sys.path.insert(0, external_dir)&lt;br /&gt;mod = __import__('op_3')&lt;br /&gt;mod.expression = expression&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;    # register this plugin using our default method&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;register_plugins()&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;    # Since it is not activated by default, we need to do it explictly&lt;/span&gt;&lt;br /&gt;activate('%')&lt;br /&gt;assert calculate("7%2") == 1&lt;br /&gt;&lt;br /&gt;print "Done!"&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;Note that we also need to import register_plugins() from base.py to make this work.&lt;br /&gt;&lt;br /&gt;That's it!  If you get the code from the &lt;a href="http://code.google.com/p/py-fun/source/browse/#svn/trunk/plugin-work"&gt;py-fun repository&lt;/a&gt;, you can try it out yourself.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-464009103972683147?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/464009103972683147/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=464009103972683147' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/464009103972683147'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/464009103972683147'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/12/plugins-part-5-activation-and.html' title='Plugins - part 5: Activation and Deactivation'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-8827253607523435992</id><published>2008-12-19T23:12:00.004-04:00</published><updated>2008-12-20T12:22:11.430-04:00</updated><title type='text'>A small svg module</title><content type='html'>&lt;span style="color: rgb(0, 153, 0);"&gt;Update: by combining suggestions made in comments, one can probably do away with much of what I describe in this blog post.  To wit:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;&lt;span style="color: rgb(0, 0, 153);"&gt;&amp;gt;&amp;gt;&amp;gt; from xml.etree import ElementTree as etree&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 0, 153);"&gt;&amp;gt;&amp;gt;&amp;gt; from functools import partial&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 0, 153);"&gt;&amp;gt;&amp;gt;&amp;gt; Circle = partial(etree.Element, 'svg:circle')&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 0, 153);"&gt;&amp;gt;&amp;gt;&amp;gt; c = Circle(cx='100', cy='200', fill='red')&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 0, 153);"&gt;&amp;gt;&amp;gt;&amp;gt; etree.tostring(c)&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 0, 153);"&gt;'&amp;lt;svg:circle cx="100" cy="200" fill="red" /&amp;gt;'&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The only minor drawback is that attributes have to be strings, whereas the module described in this post could handle integer attributes.  (Python 2.5+ required for ElementTree)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;font-size:130%;" &gt;Original post below&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size:78%;"&gt;(Time to take a break from the plugins blog series...)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.w3.org/TR/SVG11/index.html"&gt;Scalable Vector Graphics&lt;/a&gt; (SVG) are becoming more and more common on the web due to the increased support by decent browsers.  SVG specifications include basic shapes such as &lt;a href="http://www.w3.org/TR/SVG11/shapes.html#CircleElement"&gt;circle&lt;/a&gt;, &lt;a href="http://www.w3.org/TR/SVG11/shapes.html#RectElement"&gt;rect&lt;/a&gt;angles, etc., as well as supporting &lt;a href="http://www.w3.org/TR/SVG11/masking.html"&gt;clipping, masking and composition&lt;/a&gt;, &lt;a href="http://www.w3.org/TR/SVG11/filters.html"&gt;filter effects&lt;/a&gt; and much more.  Attempting to write a python-ic module supporting all possible SVG primitives and options via code like&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:130%;"&gt;test_circle = Circle(x=10, y=10, r=5, color='red')&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;can be a daunting task.  Furthermore, documenting such a module would result in a lot of duplication with the &lt;a href="http://www.w3.org/TR/SVG11/index.html"&gt;official specification&lt;/a&gt; document.  Fortunately, there is a simpler way than simply attempting to write a complete SVG module using Class-based definitions such as the one written above.  The idea is to use instead an API similar to that of &lt;a href="http://effbot.org/zone/element-index.htm"&gt;ElementTree&lt;/a&gt; (see &lt;a href="http://docs.python.org/library/xml.etree.elementtree.html"&gt;also&lt;/a&gt;) - albeit much simplified.&lt;br /&gt;&lt;br /&gt;Suppose that we would want to be able to create SVG circles, such as&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:130%;"&gt;&amp;lt;circle cx="600" cy="200" r="100" fill="red" stroke="blue" width="10"/&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;and rectangles, such as&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:130%;"&gt;&amp;lt;rect x="1" y="1" height="398" fill="none" stroke="blue" width="1198"/&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;using Python code.  A simple way to achieve this would be to define the following class:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;&lt;span style="font-weight: bold;"&gt;class XmlElement(object):&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;    '''First prototype from which all the xml elements are derived.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;       By design, this enables all elements to automatically give a&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;       text representation of themselves - it is not quite complete.'''&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;    def __init__(self, tag, **attributes):&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;        '''A basic definition that will be replaced by the specific&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;           one required by any element.'''&lt;/span&gt;&lt;br /&gt;self.tag = tag&lt;br /&gt;if attributes is not None:&lt;br /&gt;   self.attributes = attributes&lt;br /&gt;else:&lt;br /&gt;   self.attributes = {}&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;    def __repr__(self):&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;        '''This normal python method used to give a string representation&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;        for an object is used to automatically create the appropriate&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;        syntax representing an xml object.'''&lt;/span&gt;&lt;br /&gt;attrib = ["  &amp;lt;%s" % self.tag] # open tag&lt;br /&gt;for att in self.attributes:&lt;br /&gt;   attrib.append(' %s="%s"' % (att, self.attributes[att]))&lt;br /&gt;attrib.append("/&amp;gt;\n")&lt;br /&gt;return ''.join(attrib)&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;Using this class, we can create a circle instance corresponding to the definition written previously as&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;circle = XmlElement("circle", cx=600, cy=200, r=100, fill="red",&lt;br /&gt;            stroke="blue", width=10)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;This is not quite as simple as the very first Circle() class-based example we wrote but it has the advantage of supporting all possible SVG attributes.&lt;br /&gt;&lt;br /&gt;While the above XmlElement class definition is adequate for most basic SVG elements, it does not support such features as 1. text, 2. namespace (e.g. svg: prefix) and 3. grouping and sub-elements.  All three additional features can be taken care of by the following modified class definition:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;&lt;span style="font-weight: bold;"&gt;class XmlElement(object):&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;    '''Prototype from which all the xml elements are derived.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;       By design, this enables all elements to automatically give a&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;       text representation of themselves.'''&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;    def __init__(self, tag, **attributes):&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;        '''A basic definition that will be replaced by the specific&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;           one required by any element.'''&lt;/span&gt;&lt;br /&gt; self.tag = tag&lt;br /&gt; self.prefix = ""&lt;br /&gt; self.sub_elements = []&lt;br /&gt; if attributes is not None:&lt;br /&gt;     self.attributes = attributes&lt;br /&gt; else:&lt;br /&gt;     self.attributes = {}&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;    def __repr__(self):&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;        '''This normal python method used to give a string representation&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;        for an object is used to automatically create the appropriate&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;        syntax representing an xml object.'''&lt;/span&gt;&lt;br /&gt; attrib = ["  &amp;lt;%s%s"%(self.prefix, self.tag)] # open tag&lt;br /&gt; for att in self.attributes:&lt;br /&gt;     if att != 'text':&lt;br /&gt;         attrib.append(' %s="%s"' % (att, self.attributes[att]))&lt;br /&gt; if 'text' in self.attributes:&lt;br /&gt;     attrib.append("&gt;%s\n" % (self.attributes['text'],&lt;br /&gt;                                             self.prefix, self.tag))&lt;br /&gt; elif self.sub_elements:&lt;br /&gt;     attrib.append("&gt;\n")&lt;br /&gt;     for elem in self.sub_elements:&lt;br /&gt;         attrib.append("  %s" % elem)&lt;br /&gt;     attrib.append("\n" % (self.prefix, self.tag))&lt;br /&gt; else:&lt;br /&gt;     attrib.append("/&amp;gt;\n")&lt;br /&gt; return ''.join(attrib)&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;    def append(self, other):&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;        '''append other to self to create list of lists of elements'''''&lt;/span&gt;&lt;br /&gt; self.sub_elements.append(other)&lt;/span&gt;&lt;!--%s%s--&gt;&lt;!--%s%s--&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;That's &lt;span style="font-size:78%;"&gt;almost&lt;/span&gt; it!  With the exception of comments and Document Type Definition (dtd), we can use the above to create simple xhtml document containing ANY svg graphics without having to worry about xhtml syntax, opening and closing brackets, etc.   However, we can possibly do even a little better.  Consider the following xhtml document with embedded svg graphics:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;br /&gt;&amp;lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"&lt;br /&gt;"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&amp;gt;&lt;br /&gt;&amp;lt;html xmlns="http://www.w3.org/1999/xhtml"&lt;br /&gt;xmlns:svg="http://www.w3.org/2000/svg"&lt;br /&gt;xmlns:xlink="http://www.w3.org/1999/xlink"&amp;gt;&lt;br /&gt;&amp;lt;head&amp;gt;&lt;br /&gt;&amp;lt;title&amp;gt;This is the title.&amp;lt;/title&amp;gt;&lt;br /&gt;&amp;lt;/head&amp;gt;&lt;br /&gt;&amp;lt;body&amp;gt;&lt;br /&gt;&amp;lt;p&amp;gt;This is the body.&amp;lt;/p&amp;gt;&lt;br /&gt;&amp;lt;svg:svg width="0" height="0"&amp;gt;&lt;br /&gt;&amp;lt;svg:defs&amp;gt;&lt;br /&gt;&amp;lt;svg:circle cy="0" cx="0" r="20" id="red_circle" fill="red"/&amp;gt;&lt;br /&gt;&amp;lt;/svg:defs&amp;gt;&lt;br /&gt;&amp;lt;/svg:svg&amp;gt;&lt;br /&gt;&amp;lt;svg:svg width="200" height="200"&amp;gt;&lt;br /&gt;&amp;lt;svg:use xlink:href="#red_circle" transform="translate(100, 100)"/&amp;gt;&lt;br /&gt;&amp;lt;/svg:svg&amp;gt;&lt;br /&gt;&amp;lt;!-- This is a comment. --&amp;gt;&lt;br /&gt;&amp;lt;/body&amp;gt;&lt;br /&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;With just a few additional definitions, we can create this document using only Python code as follows:&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:130%;"&gt;doc = XmlDocument()&lt;br /&gt;doc.head.append(XmlElement("title", text="This is the title."))&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;# A good practice is to define svg objects, and insert them&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;# using the definition; this is overkill for this example, but it&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;# provides a test of the class.&lt;/span&gt;&lt;br /&gt;test_def = SvgDefs()&lt;br /&gt;test_def.append(SvgElement("circle", cx=0, cy=0, r=20, fill="red",&lt;br /&gt;                  id="red_circle"))&lt;br /&gt;&lt;br /&gt;doc.body.append(XmlElement("p", text="This is the body."))&lt;br /&gt;doc.body.append(test_def)&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;# we now create an svg object, that will make use of the definition above.&lt;/span&gt;&lt;br /&gt;svg_window = SvgElement("svg", width="200", height="200")&lt;br /&gt;use_circle = SvgElement("use", transform="translate(100, 100)")&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;# xlink:href can't be used as an attribute name passed to __init__&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;# this is why we use this two-step process.&lt;/span&gt;&lt;br /&gt;use_circle.attributes["xlink:href"] = "#red_circle"&lt;br /&gt;&lt;br /&gt;svg_window.append(use_circle)&lt;br /&gt;doc.body.append(svg_window)&lt;br /&gt;doc.body.append(Comment("This is a comment.")) &lt;span style="color: rgb(51, 102, 255);"&gt;# just for fun.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;print doc&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;The additional definitions are as follow:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;&lt;span style="font-weight: bold;"&gt;class XmlDocument(XmlElement):&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;    def __init__(self):&lt;/span&gt;&lt;br /&gt;   self._begin = &lt;span style="color: rgb(0, 153, 0);"&gt;"""&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;&amp;lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;&amp;lt;html xmlns="http://www.w3.org/1999/xhtml"&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;    xmlns:svg="http://www.w3.org/2000/svg"&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;    xmlns:xlink="http://www.w3.org/1999/xlink"&amp;gt;\n"""&lt;/span&gt;&lt;br /&gt;   self._end = "&amp;lt;/html&amp;gt;"&lt;br /&gt;   self.head = XmlElement("head")&lt;br /&gt;   self.body = XmlElement("body")&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;    def append(self):&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;        '''Directly appending is not allowed'''&lt;/span&gt;&lt;br /&gt;   assert False, "Append to either head or body."&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;    def __repr__(self):&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;        '''Gives an appropriate representation of an xml document.'''&lt;/span&gt;&lt;br /&gt;   return self._begin + str(self.head) + str(self.body) + self._end&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;class SvgElement(XmlElement):&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;    '''Prototype from which all the svg elements are derived.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;       By design, this enables all elements to automatically give an&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;       appropriate text representation of themselves.'''&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;    def __init__(self, tag, **attributes):&lt;/span&gt;&lt;br /&gt;   XmlElement.__init__(self, tag, **attributes)&lt;br /&gt;   self.prefix = "svg:"&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;class SvgDefs(SvgElement):&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;    '''Short-cut to create svg defs.  A user creates an instance of this&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;    object and simply appends other svg Elements'''&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;    def __init__(self):&lt;/span&gt;&lt;br /&gt;   self.defs = SvgElement("defs")&lt;br /&gt;   self.root = SvgElement("svg", width=0, height=0)&lt;br /&gt;   self.root.append(self.defs)&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;    def append(self, other):&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;        '''appends other to defs sub-element, instead of root element'''&lt;/span&gt;&lt;br /&gt;   self.defs.append(other)&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;    def __repr__(self):&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;        '''gives a string representation of an object, appropriate for&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;           insertion in an html document'''&lt;/span&gt;&lt;br /&gt;   return str(self.root)&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;class Comment(object):&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);"&gt;    '''Comment that can be inserted in code xml documents'''&lt;/span&gt;&lt;br /&gt;def __init__(self, text):&lt;br /&gt;   self.text = text&lt;br /&gt;def __repr__(self):&lt;br /&gt;   return "&amp;lt;!-- " + self.text + " --&amp;gt;\n"&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;That's it for real this time! Fewer than 100 lines of code that you can use if you need to programmatically create (x)html documents containing svg images. There are a few limitations (&lt;span style="font-size:78%;"&gt;elements containing text may not be chained...&lt;/span&gt;) but it works for me. If you want to try it yourself, you can find the module &lt;a href="http://code.google.com/p/docpicture/source/browse/trunk/src/svg.py"&gt;here&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-8827253607523435992?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/8827253607523435992/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=8827253607523435992' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/8827253607523435992'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/8827253607523435992'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/12/small-svg-module.html' title='A small svg module'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-4269833232641902543</id><published>2008-12-19T16:49:00.002-04:00</published><updated>2008-12-19T17:22:59.029-04:00</updated><title type='text'>Plugins - part 4: Crunchy-style plugin</title><content type='html'>In this 4th post in the plugins series, I will explain the approach we used in Crunchy.  While explaining the main features, I will also compare with the simple class-base plugin framework introduced in the &lt;a href="http://aroberge.blogspot.com/2008/12/plugins-part-3-simple-class-based.html"&gt;third post&lt;/a&gt; in this series.&lt;br /&gt;&lt;br /&gt;Crunchy's approach does not require a plugin to be class-based.  In fact, most plugins used in Crunchy only make use of simple functions inside modules.  While the class-based framework introduced in the third post used the fact that Python allowed automatic discovery of subclasses, the approach used in Crunchy requires an explicit registration of plugins.  Using the same example as before, this means that op_2.py would contain the following code:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;def register(OPERATORS):&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;    OPERATORS['**'] = operator_pow_token&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;class operator_pow_token(object):&lt;br /&gt; lbp = 30&lt;br /&gt; def led(self, left):&lt;br /&gt;     return left ** expression(30-1)&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Note that the class (operator_pow_token) is unchanged from the original application.&lt;br /&gt;&lt;br /&gt;The method used to find plugins is similar to that introduced previously.  The entire code required is as follows:&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:130%;"&gt;&lt;br /&gt;def init_plugins(expression):&lt;br /&gt;  plugin_dir = (os.path.dirname(os.path.realpath(__file__)))&lt;br /&gt;  plugin_files = [x[:-3] for x in os.listdir(plugin_dir) if x.endswith(".py")]&lt;br /&gt;  sys.path.insert(0, plugin_dir)&lt;br /&gt;  for plugin in plugin_files:&lt;br /&gt;      mod = __import__(plugin)&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;       if hasattr(mod, "register"):&lt;/span&gt;&lt;br /&gt;          mod.expression = expression&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;           mod.register(OPERATORS)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;By comparison, the code used in the class-based plugin could have been written as:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;def init_plugins(expression):&lt;br /&gt;   plugin_dir = os.path.dirname(os.path.realpath(__file__))&lt;br /&gt;   plugin_files = [x[:-3] for x in os.listdir(plugin_dir) if x.endswith(".py")]&lt;br /&gt;   sys.path.insert(0, plugin_dir)&lt;br /&gt;   for plugin in plugin_files:&lt;br /&gt;       mod = __import__(plugin)&lt;br /&gt;       mod.expression = expression&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;    for plugin in Plugin.__subclasses__():&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;        OPERATORS[plugin.symbol] = plugin&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;So, in one case (Crunchy-style), we have an explicit registration process with no need to create a sample base class (and, in fact, no need to work with classes at all), while in the other we have an automatic registration based on identifying subclasses.  This does not mean that the Crunchy-style is better - just different.  Both are equally good for this type of simple application.   While we have not found the approach used in Crunchy to be limiting us in any way when extending Crunchy, something must be said for the fact that all the other Python examples of plugin-based application I have found have been based on using classes.  &lt;br /&gt;&lt;br /&gt;I can now give another motivation for having chosen the small expression calculator as a candidate for a plugin-based application: since all mathematical operations were already implemented as classes, it was readily suitable for the class-based approach (and the Zope Component Architecture one, etc.) whereas all my existing code samples that used plugins (from Crunchy, docpicture, etc.) had mostly functions rather than classes in plugins.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-4269833232641902543?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/4269833232641902543/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=4269833232641902543' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/4269833232641902543'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/4269833232641902543'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/12/plugins-part-4-crunchy-style-plugin.html' title='Plugins - part 4: Crunchy-style plugin'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-1410628233954028562</id><published>2008-12-19T15:26:00.002-04:00</published><updated>2008-12-19T16:10:27.563-04:00</updated><title type='text'>Plugins - part 3: Simple class-based plugin</title><content type='html'>In the&lt;a href="http://aroberge.blogspot.com/2008/12/plugins-part-1-application.html"&gt; first post&lt;/a&gt; of this series, I introduced a simple application to be used as a demonstration of a plugin-based application.  The chosen application was an expression calculator contained in a single file. In the &lt;a href="http://aroberge.blogspot.com/2008/12/plugins-part-2-modularization.html"&gt;second post&lt;/a&gt;, I modularized the original file so that the new file structure would become a good representative of a plugin based application.  In this post, I will explain how to make use of a simple class-base plugin framework.  The model I have chosen follows fairly closely the &lt;a href="http://lucumr.pocoo.org/blogarchive/python-plugin-system"&gt;tutorial written by Armin Ronacher&lt;/a&gt;.  Another tutorial demonstrating a simple class-based plugin framework has been &lt;a href="http://gulopine.gamemusic.org/2008/jan/10/simple-plugin-framework/"&gt;written by Marty Alchin&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The first step is to define a base Plugin class.  All we need is to include the following in base.py:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;class Plugin(object):&lt;br /&gt;   pass&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Next, we ensure that classes used in plugins derive from this base class.  We only give one explicit example, that of the class included in op_2.py since the 4 classes included in op_1.py would be treated in exactly the same way.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;from plugins.base import Plugin&lt;br /&gt;&lt;br /&gt;class operator_pow_token(Plugin):&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;   symbol = '**'&lt;/span&gt;&lt;br /&gt;  lbp = 30&lt;br /&gt;  def led(self, left):&lt;br /&gt;      return left ** expression(30-1)&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;Note that we added one more line of code to the class definition.  We are now ready to deal with the plugin discovery and registration.&lt;br /&gt;&lt;br /&gt;Rather than hard-coding the information about which plugin files to import as we did when we simply modularize the application, we give a way for our program to automatically find plugins.  With the file structure that we have created, this can be accomplished as follows:&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:130%;"&gt;&lt;br /&gt;def find_plugins(expression):&lt;br /&gt;   '''find all files in the plugin directory and imports them'''&lt;br /&gt;   plugin_dir = os.path.dirname(os.path.realpath(__file__))&lt;br /&gt;   plugin_files = [x[:-3] for x in os.listdir(plugin_dir) if x.endswith(".py")]&lt;br /&gt;   sys.path.insert(0, plugin_dir)&lt;br /&gt;   for plugin in plugin_files:&lt;br /&gt;       mod = __import__(plugin)&lt;br /&gt;       &lt;span style="color: rgb(51, 102, 255);"&gt;mod.expression = expression&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;Note that the last line of code is included because of the "wart" mentioned in the previous post and would not usually be included.  To be safe, we should probably have ensured that &lt;span style="color: rgb(51, 102, 255);"&gt;expression&lt;/span&gt; was not already defined in the modules to be imported since, in theory, Python files other than plugins (such as __init__.py) might be present in the plugin directory.  In this tutorial series we will often ignore the need to insert try/except clauses to simplify the code.&lt;br /&gt;&lt;br /&gt;While we have imported the modules containing the plugins, they are not yet known in a useful form by the main application.  To do so is very simple in this class-based approach, thanks to Python's treatment of (sub-)classes.  Here's the code to do this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;def register_plugins():&lt;br /&gt; '''Register all class based plugins.&lt;br /&gt;&lt;br /&gt;    Uses the fact that a class knows about all of its subclasses&lt;br /&gt;    to automatically initialize the relevant plugins&lt;br /&gt; '''&lt;br /&gt; for plugin in Plugin.__subclasses__():&lt;br /&gt;     OPERATORS[&lt;span style="color: rgb(255, 0, 0);"&gt;plugin.symbol&lt;/span&gt;] = plugin&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;That's it!   It is hard to imagine anything simpler.  With this last definition, the entire base.py module can be written as:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;import os&lt;br /&gt;import sys&lt;br /&gt;&lt;br /&gt;OPERATORS = {}&lt;br /&gt;&lt;br /&gt;class Plugin(object):&lt;br /&gt;  pass&lt;br /&gt;&lt;br /&gt;def init_plugins(expression):&lt;br /&gt;  '''simple plugin initializer&lt;br /&gt;  '''&lt;br /&gt;  find_plugins(expression)&lt;br /&gt;  register_plugins()&lt;br /&gt;&lt;br /&gt;def find_plugins(expression):&lt;br /&gt;  '''find all files in the plugin directory and imports them'''&lt;br /&gt;  plugin_dir = os.path.dirname(os.path.realpath(__file__))&lt;br /&gt;  plugin_files = [x[:-3] for x in os.listdir(plugin_dir) if x.endswith(".py")]&lt;br /&gt;  sys.path.insert(0, plugin_dir)&lt;br /&gt;  for plugin in plugin_files:&lt;br /&gt;      mod = __import__(plugin)&lt;br /&gt;      mod.expression = expression&lt;br /&gt;&lt;br /&gt;def register_plugins():&lt;br /&gt;  '''Register all class based plugins.&lt;br /&gt;&lt;br /&gt;     Uses the fact that a class knows about all of its subclasses&lt;br /&gt;     to automatically initialize the relevant plugins&lt;br /&gt;  '''&lt;br /&gt;  for plugin in Plugin.__subclasses__():&lt;br /&gt;      OPERATORS[plugin.symbol] = plugin&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;In the next post, I will show another simple alternative approach similar to the one used in Crunchy.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-1410628233954028562?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/1410628233954028562/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=1410628233954028562' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/1410628233954028562'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/1410628233954028562'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/12/plugins-part-3-simple-class-based.html' title='Plugins - part 3: Simple class-based plugin'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-3092053815538767305</id><published>2008-12-19T12:24:00.002-04:00</published><updated>2008-12-19T13:09:57.233-04:00</updated><title type='text'>Plugins - part 2: modularization</title><content type='html'>In the &lt;a href="http://aroberge.blogspot.com/2008/12/plugins-part-1-application.html"&gt;first post&lt;/a&gt; on the Plugins series, I introduced the small application used to demonstrate how one could modularize applications using a plugin architecture.   The digital ink was barely dry on that post that already two people rose to the challenge and presented their solution, one using the &lt;a href="http://www.plope.com/Members/chrism/pluginizing_an_app"&gt;standard method with the Zope Component Architecture&lt;/a&gt;, the other a &lt;a href="http://regebro.wordpress.com/2008/12/19/the-plugin-architecture-bashout-grok/"&gt;modified method&lt;/a&gt; using &lt;a href="http://grok.zope.org/"&gt;grok&lt;/a&gt;.  I will comment on these two solutions later in this series.&lt;br /&gt;&lt;br /&gt;With apologies to the more advanced users, I have decided to proceed fairly slowly and cover many simple concepts with this series of plugins.  Thus, this second post will not yet discuss plugins, but simply lay the groundwork for future posts.  By the way, for those interested, and as pointed out by Lennart Regebro in his post, all the code samples that I will use can be browsed at, or retrieved from, my &lt;a href="http://code.google.com/p/py-fun/source/browse/trunk/plugin-work/"&gt;py-fun google code repository&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;As a first step before comparing different approaches to dealing with plugins, I will take the sample application introduced in the first post and modularize it.&lt;br /&gt;&lt;br /&gt;The core application (calculator.py) is as follows:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;import re&lt;br /&gt;&lt;br /&gt;from plugins.base import &lt;span style="color: rgb(255, 0, 0);"&gt;OPERATORS&lt;/span&gt;, &lt;span style="color: rgb(51, 204, 0);"&gt;init_plugins&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;class literal_token(object):&lt;br /&gt;  def __init__(self, value):&lt;br /&gt;      self.value = value&lt;br /&gt;  def nud(self):&lt;br /&gt;      return self.value&lt;br /&gt;&lt;br /&gt;class end_token(object):&lt;br /&gt;  lbp = 0&lt;br /&gt;&lt;br /&gt;def tokenize(program):&lt;br /&gt;  for number, operator in re.findall("\s*(?:(\d+)|(\*\*|.))", program):&lt;br /&gt;      if number:&lt;br /&gt;          yield literal_token(int(number))&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;        elif operator in OPERATORS:&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;            yield OPERATORS[operator]()&lt;/span&gt;&lt;br /&gt;      else:&lt;br /&gt;          raise SyntaxError("unknown operator: %r" % operator)&lt;br /&gt;  yield end_token()&lt;br /&gt;&lt;br /&gt;def expression(rbp=0):&lt;br /&gt;  global token&lt;br /&gt;  t = token&lt;br /&gt;  token = next()&lt;br /&gt;  left = t.nud()&lt;br /&gt;  while rbp &amp;lt; token.lbp:&lt;br /&gt;      t = token&lt;br /&gt;      token = next()&lt;br /&gt;      left = t.led(left)&lt;br /&gt;  return left&lt;br /&gt;&lt;br /&gt;def calculate(program):&lt;br /&gt;  global token, next&lt;br /&gt;  next = tokenize(program).next&lt;br /&gt;  token = next()&lt;br /&gt;  return expression()&lt;br /&gt;&lt;br /&gt;if __name__ == "__main__":&lt;br /&gt;  &lt;span style="color: rgb(51, 204, 0);"&gt;init_plugins(expression)&lt;/span&gt;&lt;br /&gt;  assert calculate("+1") == 1&lt;br /&gt;  assert calculate("-1") == -1&lt;br /&gt;  assert calculate("10") == 10&lt;br /&gt;  assert calculate("1+2") == 3&lt;br /&gt;  assert calculate("1+2+3") == 6&lt;br /&gt;  assert calculate("1+2-3") == 0&lt;br /&gt;  assert calculate("1+2*3") == 7&lt;br /&gt;  assert calculate("1*2+3") == 5&lt;br /&gt;  assert calculate("6*2/3") == 4&lt;br /&gt;  assert calculate("2**3") == 8&lt;br /&gt;  assert calculate("2*2**3") == 16&lt;br /&gt;  print "Done!"&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;For the next few posts, when I demonstrate some very simple plugin approaches, this core application will remain untouched.  This is one important characteristic of plugin-based application: in a well-designed application, plugin writers should not have to modify a single line of the core modules to ensure that their plugins can be used.&lt;br /&gt;&lt;br /&gt;Communication between plugins and the core application is ensured via an Application Programming Interface (API) unique to that application.  In our example, the API is a simple Python dict (OPERATORS) written in capital letters only to make it stand out.&lt;br /&gt;&lt;br /&gt;In a sub-directory (plugins), in addition to an empty __init__.py file, we include the following three files:&lt;br /&gt;&lt;br /&gt;1. base.py&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;OPERATORS = {}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 204, 0);"&gt;def init_plugins(expression):&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 204, 0);"&gt;    '''simulated plugin initializer'''&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 204, 0);"&gt;    from plugins import op_1, op_2&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;    op_1.expression = expression&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);"&gt;    op_2.expression = expression&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 204, 0);"&gt;    OPERATORS['+'] = op_1.operator_add_token&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 204, 0);"&gt;    OPERATORS['-'] = op_1.operator_sub_token&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 204, 0);"&gt;    OPERATORS['*'] = op_1.operator_mul_token&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 204, 0);"&gt;    OPERATORS['/'] = op_1.operator_div_token&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 204, 0);"&gt;    OPERATORS['**'] = op_2.operator_pow_token&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;2. op_1.py&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 51, 255);font-size:130%;" &gt;class operator_add_token(object):&lt;br /&gt;   lbp = 10&lt;br /&gt;   def nud(self):&lt;br /&gt;       return expression(100)&lt;br /&gt;   def led(self, left):&lt;br /&gt;       return left + expression(10)&lt;br /&gt;&lt;br /&gt;class operator_sub_token(object):&lt;br /&gt;   lbp = 10&lt;br /&gt;   def nud(self):&lt;br /&gt;       return -expression(100)&lt;br /&gt;   def led(self, left):&lt;br /&gt;       return left - expression(10)&lt;br /&gt;&lt;br /&gt;class operator_mul_token(object):&lt;br /&gt;   lbp = 20&lt;br /&gt;   def led(self, left):&lt;br /&gt;       return left * expression(20)&lt;br /&gt;&lt;br /&gt;class operator_div_token(object):&lt;br /&gt;   lbp = 20&lt;br /&gt;   def led(self, left):&lt;br /&gt;       return left / expression(20)&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;and 3. op_2.py&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;span style="color: rgb(51, 102, 255);font-size:130%;" &gt;class operator_pow_token(object):&lt;br /&gt;   lbp = 30&lt;br /&gt;   def led(self, left):&lt;br /&gt;       return left ** expression(30-1)&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The last two files have been simply extracted with no modification from the original application.  Instead of having 2 such files containing classes of the form operator_xxx_token, I could have included them all in one file, or split into 5 different files.  The number of files is irrelevant here: they are only introduced to play the role of plugins in this application.&lt;br /&gt;&lt;br /&gt;The file base.py plays the role here of a plugin initialization module: it ensures that plugins are properly registered and made available to the core program.&lt;br /&gt;&lt;br /&gt;Since I wanted to change the original code as little as possible, a "wart" is present in the code as written since it was never intended to be a plugin-based application: the function expression() was accessible to all objects in the initial single-file application.  It is now needed in a number of modules.  The file base.py takes care of ensuring that "plugin" modules have access to that function in a transparent way.   This will need to be changed when using some standard plugin frameworks, as was done in the &lt;a href="http://www.plope.com/Members/chrism/pluginizing_an_app"&gt;zca example&lt;/a&gt; or the &lt;a href="http://regebro.wordpress.com/2008/12/19/the-plugin-architecture-bashout-grok/"&gt;grok one&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;In the next post, I will show how to take this now modularized application and transform it into a proper plugin-based one.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-3092053815538767305?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/3092053815538767305/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=3092053815538767305' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/3092053815538767305'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/3092053815538767305'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/12/plugins-part-2-modularization.html' title='Plugins - part 2: modularization'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-1486770776274669852</id><published>2008-12-18T20:41:00.003-04:00</published><updated>2008-12-18T21:29:29.334-04:00</updated><title type='text'>Plugins - part 1: the application</title><content type='html'>My interest in plugins started two years ago listening to &lt;a href="http://radian.org/notebook/"&gt;Ivan Krstić&lt;/a&gt; talk about the OLPC. Following his talk, I wrote the following on &lt;a href="http://mail.python.org/pipermail/edu-sig/2007-February/007771.html"&gt;edu-sig&lt;/a&gt;:&lt;br /&gt;&lt;blockquote style="font-style: italic;"&gt;One open issue (as I understand it) is that of finding the "best practice" for &lt;span class="nfakPe"&gt;plugins&lt;/span&gt;.  The idea is that the core programs should be as small as possible but easy to extend via &lt;span class="nfakPe"&gt;plugins&lt;/span&gt;.  I thought that there already was a "well known and best way" to design &lt;span class="nfakPe"&gt;plugins&lt;/span&gt; - and it was on my list of things to learn about (to eventually incorporate rur-ple within crunchy).&lt;/blockquote&gt;After discussing this off-list with Johannes Woolard, I concluded that we should try to redesign Crunchy to make use of plugins.  While I was thinking about how we might proceed to do this, Johannes went ahead and &lt;a href="http://pytute.blogspot.com/2007/04/python-plugin-system.html"&gt;implemented a simple plugin&lt;/a&gt; framework which we eventually adopted for Crunchy.&lt;br /&gt;&lt;br /&gt;While there are a few agreed-upon "standards" when it comes to dealing with plugins in Python (such as &lt;a href="http://www.blogger.com/peak.telecommunity.com/DevCenter/setuptools"&gt;setuptools&lt;/a&gt; and &lt;a href="http://www.muthukadan.net/docs/zca.html"&gt;Zope Component Architecture&lt;/a&gt;), I tend to agree with Ivan Krstić's observation that there are no "best practice" for plugins - at least, none that I have seen documented.  As what might be considered to be a first step in determining the "best practice" for writing plugin-based applications with Python, I will take a sample application, small enough so that it can be completely included and described in a blog post, and &lt;span style="font-style: italic;"&gt;&lt;span style="font-weight: bold;"&gt;not&lt;/span&gt;&lt;/span&gt; written with plugins in mind.  I thought it would be a more representative example to use an arbitrary sample application, rather than trying to come up with one specifically written for the purpose of this series of post.&lt;br /&gt;&lt;br /&gt;The application I have chosen is a small modification of an &lt;a href="http://effbot.org/zone/simple-top-down-parsing.htm"&gt;expression calculator&lt;/a&gt; written and described by Fredrik Lundh, aka effbot, a truly outstanding pythonista.  The entire code is as follows:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;""" A simple expression calculator entirely contained in a single file.&lt;br /&gt;&lt;br /&gt;See http://effbot.org/zone/simple-top-down-parsing.htm for detailed explanations&lt;br /&gt;as to how it works.&lt;br /&gt;&lt;br /&gt;This is the basic application used to demonstrate various plugin frameworks.&lt;br /&gt;"""&lt;br /&gt;&lt;br /&gt;import re&lt;br /&gt;&lt;br /&gt;class literal_token(object):&lt;br /&gt;   def __init__(self, value):&lt;br /&gt;       self.value = value&lt;br /&gt;   def nud(self):&lt;br /&gt;       return self.value&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;class operator_add_token(object):&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;    lbp = 10&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;    def nud(self):&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;        return expression(100)&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;    def led(self, left):&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;        return left + expression(10)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;class operator_sub_token(object):&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;    lbp = 10&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;    def nud(self):&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;        return -expression(100)&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;    def led(self, left):&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;        return left - expression(10)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;class operator_mul_token(object):&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;    lbp = 20&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;    def led(self, left):&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;        return left * expression(20)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;class operator_div_token(object):&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;    lbp = 20&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;    def led(self, left):&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;        return left / expression(20)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;class operator_pow_token(object):&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;    lbp = 30&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;    def led(self, left):&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;        return left ** expression(30-1)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;class end_token(object):&lt;br /&gt;   lbp = 0&lt;br /&gt;&lt;br /&gt;def tokenize(program):&lt;br /&gt;   for number, operator in re.findall("\s*(?:(\d+)|(\*\*|.))", program):&lt;br /&gt;       if number:&lt;br /&gt;           yield literal_token(int(number))&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;        elif operator == "+":&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;            yield operator_add_token()&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;        elif operator == "-":&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;            yield operator_sub_token()&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;        elif operator == "*":&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;            yield operator_mul_token()&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;        elif operator == "/":&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;            yield operator_div_token()&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;        elif operator == "**":&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;            yield operator_pow_token()&lt;/span&gt;&lt;br /&gt;        else:&lt;br /&gt;           raise SyntaxError("unknown operator: %r" % operator)&lt;br /&gt;   yield end_token()&lt;br /&gt;&lt;br /&gt;def expression(rbp=0):  # note that expression is a global object in this module&lt;br /&gt;   global token&lt;br /&gt;   t = token&lt;br /&gt;   token = next()&lt;br /&gt;   left = t.nud()&lt;br /&gt;   while rbp &lt; token.lbp:&lt;br /&gt;       t = token&lt;br /&gt;       token = next()&lt;br /&gt;       left = t.led(left)&lt;br /&gt;   return left&lt;br /&gt;&lt;br /&gt;def calculate(program):&lt;br /&gt;   global token, next&lt;br /&gt;   next = tokenize(program).next&lt;br /&gt;   token = next()&lt;br /&gt;   return expression()&lt;br /&gt;&lt;br /&gt;if __name__ == "__main__":&lt;br /&gt;   assert calculate("+1") == 1&lt;br /&gt;   assert calculate("-1") == -1&lt;br /&gt;   assert calculate("10") == 10&lt;br /&gt;   assert calculate("1+2") == 3&lt;br /&gt;   assert calculate("1+2+3") == 6&lt;br /&gt;   assert calculate("1+2-3") == 0&lt;br /&gt;   assert calculate("1+2*3") == 7&lt;br /&gt;   assert calculate("1*2+3") == 5&lt;br /&gt;   assert calculate("6*2/3") == 4&lt;br /&gt;   assert calculate("2**3") == 8&lt;br /&gt;   assert calculate("2*2**3") == 16&lt;br /&gt;   print "Done!"&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;The latest version used can be &lt;a href="http://code.google.com/p/py-fun/source/browse/trunk/plugin-work/original/single_file.py"&gt;found online&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;In the above code, I have highlighted &lt;span style="color: rgb(255, 0, 0);"&gt;in red&lt;/span&gt; classes that will be transformed into plugins.  I have also highlighted &lt;span style="color: rgb(51, 204, 0);"&gt;in green&lt;/span&gt; hard-coded if/elif choices that will become indirect references to the plugin components.&lt;br /&gt;&lt;br /&gt;In the next post in this series, I will break up this single file in a set of different modules as a required preliminary step before transforming the whole applications into a plugin-based one, with a small core.  In subsequent posts, I will keep the core constant and compare various approaches that one can use to link the plugins with the core.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-1486770776274669852?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/1486770776274669852/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=1486770776274669852' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/1486770776274669852'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/1486770776274669852'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/12/plugins-part-1-application.html' title='Plugins - part 1: the application'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-5820229195030135059</id><published>2008-12-17T21:04:00.002-04:00</published><updated>2008-12-17T21:54:26.200-04:00</updated><title type='text'>Seeing double at Pycon 2009</title><content type='html'>&lt;a href="http://jessenoller.com/2008/12/16/pycon-2009-talks-accepted/"&gt;Jesse Noller&lt;/a&gt; is going to give two talks at &lt;a href="http://us.pycon.org/2009"&gt;Pycon 2009&lt;/a&gt;.  So is &lt;a href="http://tarekziade.wordpress.com/2008/12/16/pycon-2009-talks/"&gt;Tarek Ziadé&lt;/a&gt;. And &lt;a href="http://blog.vrplumber.com/index.php?/archives/2243-Two-Talks-at-PyCon.html"&gt;Mike Fletcher&lt;/a&gt; is as well.  And &lt;a href="http://sayspy.blogspot.com/2008/12/my-pycon-panel-has-been-accepted.html"&gt;Brett Cannon&lt;/a&gt; has a talk and a panel.  So far there I have not seen any post on Planet Python about someone giving just one talk.&lt;br /&gt;&lt;br /&gt;I would hate to be the one breaking the streak.  So, I might as well announce that I will be giving two talks as well. :-)&lt;br /&gt;&lt;br /&gt;Not surprisingly, the first one is about &lt;a href="http://code.google.com/p/crunchy"&gt;Crunchy&lt;/a&gt;.  The title of the talk is &lt;span style="font-style: italic;"&gt;Learning and Teaching Python Programming: The Crunchy Way&lt;/span&gt;, and the abstract reads as follows:&lt;br /&gt;&lt;blockquote style="font-style: italic;"&gt;Crunchy (&lt;a href="http://code.google.com/p/crunchy" rel="nofollow"&gt;http://code.google.com/p/crunchy&lt;/a&gt;) is a program that transforms a static Python tutorial into an interactive session within a browser. In this talk, I will present Crunchy, focusing on the features that are specifically designed to be helpful in a formal teaching setting.&lt;/blockquote&gt;&lt;br /&gt;Not exactly Earth-shattering but hopefully of interest to anyone that has to teach programming in a formal setting or who would just be interested in showing off Python to anyone.  This Crunchy talk is, of course, not going to be your traditional slide-based talk but rather more like an interactive demo using Crunchy.  I am hoping to have a few surprises by the time the conference occurs.&lt;br /&gt;&lt;br /&gt;My other talk is going to be very different.  I doubt very much that I will be using Crunchy for it.  The title is &lt;span style="font-style: italic;"&gt;Plugins and Monkeypatching: increasing flexibility, dealing with inflexibility, &lt;/span&gt;and the abstract reads as follows:&lt;br /&gt;&lt;br /&gt;&lt;blockquote style="font-style: italic;"&gt;By using plugins, one can create software that is easily extensible by others, thereby promoting collaborative development. The flip side of extensible software occurs when dealing with some standard framework whose interface is closed but which does not do exactly what is desired. In this case, monkeypatching may be worth considering. &lt;/blockquote&gt;&lt;blockquote style="font-style: italic;"&gt;In this talk, I'll give concrete examples of both plugin design and using monkeypatching, using small code samples from existing projects, and discuss the advantages and the shortcomings of the methods used. I will also include the design of a tiny, but flexible module for generating svg code - and compare it with other existing approaches.&lt;/blockquote&gt;I can not pretend to even come close to being an expert about designing plugin based applications.  Still, I felt that I have had some potentially useful experiences to share about these topics which motivated my talk proposal.  Now that it has been accepted, I have started working on fleshing out the original outline.&lt;br /&gt;&lt;br /&gt;In preparation for the actual talk, which will not go into much code details due to time constraints, I plan to start a short series of posts about plugins.  In the first post I will give an overview of a simple application (a calculator) that is written as a single file.  In the second post, I will reorganize the code so as to use multiple files, with a number of modules located in a "plugins" directory, laying out the groundwork for working with actual plugins.  Subsequent posts will be used to demonstrate different approaches used to transform the application into a truly plugin-based one.&lt;br /&gt;&lt;br /&gt;Of course, the plugin model used in Crunchy will be one approach showcased.  A second one (which I have already implemented) is a simple class based one inspired by a &lt;a href="http://lucumr.pocoo.org/blogarchive/python-plugin-system"&gt;tutorial written by Armin Romacher&lt;/a&gt;.  I also plan to demonstrate how to use the Zope component architecture approach as well as the &lt;a href="http://peak.telecommunity.com/DevCenter/setuptools"&gt;setuptools&lt;/a&gt; based method (and possibly others depending on suggestions I might receive).&lt;br /&gt;&lt;br /&gt;Since I have never actually written any code using the Zope component architecture or the setuptools based approach, I thought it would be interesting to do this in a truly open-source spirit.  Therefore, once I have written the first two or three posts in this series, I would like to invite anyone interested to contribute their own code demonstrating their favourite framework.  This way, experts could make sure that their favourite framework is properly showcased, and not misrepresented by me.  Interested parties can contribute either by sending me the code directly or by blogging about it.  (If your blog appears on either planet.python.org or planetpython.org, I will most likely read it.) &lt;br /&gt;&lt;br /&gt;Anyone who contributes in this way to my talk will be mentioned at Pycon AND receive half of the stipend I get as a presenter. ;-)&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;/blockquote&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-5820229195030135059?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/5820229195030135059/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=5820229195030135059' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/5820229195030135059'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/5820229195030135059'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/12/seeing-double-at-pycon-2009.html' title='Seeing double at Pycon 2009'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-1575408275645675478</id><published>2008-11-28T22:46:00.002-04:00</published><updated>2008-11-28T23:18:11.856-04:00</updated><title type='text'>Thwarted by lack of speed</title><content type='html'>I was hoping to make an announcement of a new cool app based on &lt;a href="http://code.google.com/appengine/"&gt;Google's App Engine&lt;/a&gt; but unfortunately I have been thwarted by Python's relative lack of speed.&lt;br /&gt;&lt;br /&gt;I have started working on a new version of Crunchy that would run as a web app on Google's servers.  While the current version of Crunchy fetches existing html pages, processes them and displays them in the browser, this new version would retrieve html page content (in reStructuredText format) from Google's datastore, transform it into html, process it to add interactive elements, and then displays them.&lt;br /&gt;&lt;br /&gt;This new app was going to be usable as a wiki to create new material.  This was my starting point, greatly helped by an already existing wiki example that I adapted to use reStructuredText.  When requesting a page, the following was supposed to happen:&lt;br /&gt;&lt;br /&gt;1. reStructuredText content (for the body of the html page) is fetched from the datastore.&lt;br /&gt;2. said content is transformed (by docutils) into html&lt;br /&gt;3. html content is further processed by modified "crunchy engine" to add interactive elements.&lt;br /&gt;4. modified html content is inserted in page template and made available.&lt;br /&gt;&lt;br /&gt;The user would then be able to enter some Python code which could be send back to the App Engine using Ajax for processing and updating the page display.&lt;br /&gt;&lt;br /&gt;A normal user would only be able to interact with already existing pages.  Special users ("editors") only would have been able to add pages.  I was hoping that people teaching Python would be interested in writing doctest-based exercises and that a useful collection could be implemented over time.&lt;br /&gt;&lt;br /&gt;Unfortunately, this approach can not work, at least not using Google's App Engine on Google's own servers.  :-(&lt;br /&gt;&lt;br /&gt;Just playing with &lt;span style="font-style: italic;"&gt;&lt;span style="font-weight: bold;"&gt;small&lt;/span&gt;&lt;/span&gt; pages, steps 1 and 2 are long enough that I get warnings logged mentioning that requests are taking too long.  I know from experience that step 3 (which I have not started to implement/port from the standard Crunchy) can take even longer for reasonably size pages.  So, this does not appear to be feasible ... which is unfortunate.&lt;br /&gt;&lt;br /&gt;I think I will continue to develop this app to be used as a local one and perhaps write a second wiki-based app that would take html code with no further processing.  I could use the first one to create a page, have it processed and use the "view source" feature of Firefox to cut and paste the content into the online app.  This would remove the need for any processing of pages on Google's servers - only Python code execution would need to be taken care of.  (Of course, a user &lt;span style="font-style: italic;"&gt;could&lt;/span&gt; enter some code sample that would take too long to execute and hit Google's time limit ...)&lt;br /&gt;&lt;br /&gt;If anyone has a better idea, feel free to leave it as a comment.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-1575408275645675478?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/1575408275645675478/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=1575408275645675478' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/1575408275645675478'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/1575408275645675478'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/11/thwarted-by-lack-of-speed.html' title='Thwarted by lack of speed'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-4910988611096423383</id><published>2008-11-01T00:59:00.003-03:00</published><updated>2008-11-01T01:07:25.825-03:00</updated><title type='text'>docpicture progress</title><content type='html'>For those interested, docpicture can now display images from the web.  There's also a somewhat silly example where I embedded the code for a matplotlib example inside a docstring and have it displayed as a plot when viewing the docstring via docpicture inside a web browser.  In order to do so I had to exec the code which is not exactly good practice ... but it serves to highlight the need to either only allow "parsers" from the standard distribution &lt;span style="font-weight: bold; font-style: italic;"&gt;or&lt;/span&gt; require the user to give permission to a parser to be able to register itself with docpicture while it is running.  I chose this second approach, although if you run the demo, you will not be given the opportunity to approve or not the parser - it will be done for you.  This may need to be revisited...&lt;br /&gt;&lt;br /&gt;I just announced a &lt;a href="http://mail.python.org/pipermail/python-list/2008-November/514134.html"&gt;new release&lt;/a&gt; on the Python list.  You can get docpicture 0.2 from &lt;a href="http://code.google.com/p/docpicture/"&gt;here&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-4910988611096423383?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/4910988611096423383/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=4910988611096423383' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/4910988611096423383'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/4910988611096423383'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/10/docpicture-progress.html' title='docpicture progress'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-6790578728995645299</id><published>2008-10-29T22:28:00.002-03:00</published><updated>2008-10-29T22:39:22.767-03:00</updated><title type='text'>svg mathematical equation</title><content type='html'>Ok, it's done: mathematical equations generated dynamically and displayed as svg graphics.  Only using the standard Python library ... and one "tiny" additional download: matplotlib.  &lt;a href="http://andre.roberge.googlepages.com/equation.xhtml"&gt;Here's the first result&lt;/a&gt;  (saved as a "hard-copy"; you may have to download the page and reopen it locally using Firefox.)&lt;br /&gt;&lt;br /&gt;Note: do not bother looking for the files in the "py-fun" repository where I had the first release of docpicture.  I will clean up things a bit and do a new release from a different place.&lt;br /&gt;&lt;br /&gt;As usual, comments &amp;amp; suggestions are welcome.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-6790578728995645299?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/6790578728995645299/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=6790578728995645299' title='9 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/6790578728995645299'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/6790578728995645299'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/10/svg-mathematical-equation.html' title='svg mathematical equation'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>9</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-3541542058719393345</id><published>2008-10-28T17:26:00.002-03:00</published><updated>2008-10-28T17:36:09.921-03:00</updated><title type='text'>docpicture and uml sequence diagrams</title><content type='html'>In a &lt;a href="http://aroberge.blogspot.com/2008/10/more-on-docpictures-and-almost-minimal.html"&gt;previous post&lt;/a&gt; about docpicture, I gave an example of a graphics generated from &lt;a href="http://www.websequencediagrams.com/"&gt;this site&lt;/a&gt; as something that would be desirable to do.  (You can find &lt;a href="http://www.websequencediagrams.com/examples.html"&gt;more examples here&lt;/a&gt;.)  Well, it turned out to be easy to do ... at the cost of a server connection.  I used the &lt;a href="http://www.websequencediagrams.com/embedding.html"&gt;example given&lt;/a&gt; to embed a graphics inside a page and ... voilà, it is done.  As long as one has a live internet connection (and assuming the websequencediagram server is not down), a graphics is generated as requested.&lt;br /&gt;&lt;br /&gt;Eventually, I still would like to implement my own parser to create svg code for uml sequence diagrams rather than relying on an external service.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-3541542058719393345?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/3541542058719393345/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=3541542058719393345' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/3541542058719393345'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/3541542058719393345'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/10/docpicture-and-uml-sequence-diagrams.html' title='docpicture and uml sequence diagrams'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-3213111914079384733</id><published>2008-10-27T23:47:00.001-03:00</published><updated>2008-10-27T23:49:48.671-03:00</updated><title type='text'>docpicture: initial release</title><content type='html'>The subject line says it all.  It's a small download: less than 22 kB, available from &lt;a href="http://code.google.com/p/py-fun/"&gt;here&lt;/a&gt;.  Feedback and suggestions are definitely welcome.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-3213111914079384733?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/3213111914079384733/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=3213111914079384733' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/3213111914079384733'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/3213111914079384733'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/10/docpicture-initial-release.html' title='docpicture: initial release'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-1188100654085777377</id><published>2008-10-26T23:53:00.004-03:00</published><updated>2008-10-27T07:36:52.879-03:00</updated><title type='text'>docpicture: working ... and a query.</title><content type='html'>docpicture (&lt;a href="http://aroberge.blogspot.com/2008/10/docpicture-getting-closer.html"&gt;see&lt;/a&gt; &lt;a href="http://aroberge.blogspot.com/2008/10/docpicture-svg-generation-first.html"&gt;previous&lt;/a&gt; &lt;a href="http://aroberge.blogspot.com/2008/10/more-on-docpictures-and-almost-minimal.html"&gt;posts&lt;/a&gt;) is now working as a full prototype.  By this, I mean that instead of doing&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;gt;&amp;gt;&amp;gt; help(some_object)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;at the Python prompt, one can do&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;gt;&amp;gt;&amp;gt; from docpicture import view&lt;br /&gt;&amp;gt;&amp;gt;&amp;gt; view(some_object)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;and some_object's docstring will be displayed in your webbrowser, with &lt;span style="font-style: italic; font-weight: bold;"&gt;any&lt;/span&gt; docpicture directive being translated so as to embed a nice picture.  Well, by "&lt;span style="font-weight: bold; font-style: italic;"&gt;any&lt;/span&gt;", I mean any turtle directive conforming to the limited syntax I have included.&lt;br /&gt;&lt;br /&gt;When I compare the output of help() with that of docpicture.view(), I am struck at how much more information than simply the object's docstring is included.  I have tried (briefly) to play with the pydoc module to see if I could &lt;span style="font-style: italic; font-weight: bold;"&gt;redirect the output of help() to a string&lt;/span&gt; that I could process with docpicture.view() ... but to no avail.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold; font-style: italic;"&gt;If anyone knows how I could do this simply, I would be very grateful&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;docpicture is going to be released (version 0.1) as soon as I complete a decent "readme" file.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;--UPDATE-- &lt;/span&gt;Ok, after playing some more with pydoc, I found out how to do this.&lt;br /&gt;&lt;br /&gt;In my module, I do the following:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;import pydoc&lt;br /&gt;from StringIO import StringIO&lt;br /&gt;my_stdin = StringIO()&lt;br /&gt;def my_pager(text):&lt;br /&gt;   my_stdin.write(pydoc.plain(text))&lt;br /&gt;   return&lt;br /&gt;pydoc.pager = my_pager&lt;br /&gt;&lt;br /&gt;pydoc.help(obj)&lt;br /&gt;retrieved = my_stdin.getvalue()&lt;br /&gt;my_stdin.close()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;and use the retrieved text as I wish.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-1188100654085777377?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/1188100654085777377/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=1188100654085777377' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/1188100654085777377'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/1188100654085777377'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/10/docpicture-working-and-query.html' title='docpicture: working ... and a query.'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-4866687630117484635</id><published>2008-10-24T00:28:00.003-03:00</published><updated>2008-10-24T00:35:33.200-03:00</updated><title type='text'>docpicture: getting closer</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_KpSgL5zKAvE/SQFB7kB64BI/AAAAAAAAABU/J93rC4L0vZk/s1600-h/turtle2.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 120px;" src="http://4.bp.blogspot.com/_KpSgL5zKAvE/SQFB7kB64BI/AAAAAAAAABU/J93rC4L0vZk/s400/turtle2.png" alt="" id="BLOGGER_PHOTO_ID_5260558331285266450" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_KpSgL5zKAvE/SQFB7ZubITI/AAAAAAAAABM/62uDo2b_Zlc/s1600-h/turtle1.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 222px;" src="http://4.bp.blogspot.com/_KpSgL5zKAvE/SQFB7ZubITI/AAAAAAAAABM/62uDo2b_Zlc/s400/turtle1.png" alt="" id="BLOGGER_PHOTO_ID_5260558328519139634" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;This is just a progress report for the curious among you: the previous two images were generated automatically from the docpicture code written above them.  If everything goes well, by the end of the weekend I'll be ready to give a sneak preview of the code to anyone interested.  Feel free to contact me.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-4866687630117484635?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/4866687630117484635/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=4866687630117484635' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/4866687630117484635'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/4866687630117484635'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/10/docpicture-getting-closer.html' title='docpicture: getting closer'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_KpSgL5zKAvE/SQFB7kB64BI/AAAAAAAAABU/J93rC4L0vZk/s72-c/turtle2.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-2084536915084490634</id><published>2008-10-19T19:24:00.005-03:00</published><updated>2008-10-19T23:59:22.662-03:00</updated><title type='text'>docpicture + svg generation: first prototype working</title><content type='html'>As outlined in a previous post, I have decided to use svg to embed pictures in html pages generated from docstrings.  Of course, this could be generalized to other cases than docstrings; for example, this could be implemented as a reStructuredText directive.  In the course of playing with generating such pages with inline svg code, I observed the following:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;If a file is saved locally and loaded within Firefox, it should be saved with a ".xml" (or possibly ".xhtml") extension.&lt;/li&gt;&lt;li&gt;If a file is served dynamically from a server, all that is needed is that its content be identified as "application/xhtml+xml"  [as I had mentioned previously].&lt;/li&gt;&lt;li&gt;If the file is put on a "generic webserver" that can't be configured by the user, Firefox will ignore the svg code if the extension of the file is ".xml" or ".html".  However, I did find a workaround: use a ".xhtml" extension and, when prompted by Firefox as to what application to use to open such file, select Firefox itself.  The file will be downloaded locally and displayed correctly. At least, this is what happens on a Mac with Firefox 3.&lt;/li&gt;&lt;/ol&gt;There might be another way to do this; if so, I would be interested in knowing how.  In the meantime, for those interested, &lt;a href="http://andre.roberge.googlepages.com/docpicture.xhtml"&gt;here is the output of a first working test case&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Update:&lt;/span&gt; The test case has been improved with styling.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Update 2:&lt;/span&gt; &lt;a href="http://andre.roberge.googlepages.com/turtle_test.xhtml"&gt;A new picture&lt;/a&gt; perhaps gives a better idea of a more realistic use case.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-2084536915084490634?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/2084536915084490634/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=2084536915084490634' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/2084536915084490634'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/2084536915084490634'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/10/docpicture-svg-generation-first.html' title='docpicture + svg generation: first prototype working'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-29924202986071637</id><published>2008-10-19T11:46:00.002-03:00</published><updated>2008-10-19T12:05:33.950-03:00</updated><title type='text'>Seeking advice on parsing</title><content type='html'>Dear Lazyweb,&lt;br /&gt;&lt;br /&gt;As a follow-up to my &lt;a href="http://aroberge.blogspot.com/2008/10/more-on-docpictures-and-almost-minimal.html"&gt;previous post&lt;/a&gt;, I have a question...&lt;br /&gt;&lt;br /&gt;Suppose you wanted to design an application that used parsing as a core element and you wanted this application to be easily extended by users.  Furthermore, you were hoping that the users would contribute back some parsers that could be included in future versions. Would you:&lt;br /&gt;&lt;br /&gt;1a) Use &lt;a href="http://pyparsing.wikispaces.com/"&gt;pyparsing&lt;/a&gt; and require all potential users of your application to download it separately.&lt;br /&gt;1b) Use pyparsing but include it bundled with your application.&lt;br /&gt;2) Use regular expressions (re module in standard library) and expect everyone to do the same.&lt;br /&gt;3) Use some other module in the standard library.&lt;br /&gt;4) Use some other 3rd party parsing package.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-29924202986071637?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/29924202986071637/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=29924202986071637' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/29924202986071637'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/29924202986071637'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/10/seeking-advice-on-parsing.html' title='Seeking advice on parsing'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-4199719444912289975</id><published>2008-10-18T14:24:00.005-03:00</published><updated>2008-10-18T16:43:18.726-03:00</updated><title type='text'>More on docpictures and (almost) minimal example of web server with inline svg</title><content type='html'>After playing some more with the idea of embedding pictures in docstrings, I've settled on using dynamically created svg images instead of png images.  The basic idea (which I'll write about in more details later) is to have something like&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;.. docpictures:: some_type&lt;br /&gt;highly readable description&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;and have the docpicture module parse the "highly readable description" based on the syntax defined in some_type. Note that I chose this notation to be compatible with reStructuredText directives.   The "highly readable description" will depend on the context.  For example, the mathematically inclined will be able to read this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;.. docpictures:: equation&lt;br /&gt;e^{i\pi} = -1&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;while the following might be easy to understand by programmers:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;..docpicture:: uml_sequence&lt;br /&gt;User --&gt; Browser: clicks on a link&lt;br /&gt;Browser -&gt; Crunchy: requests file&lt;br /&gt;Crunchy --&gt; Browser: Authentication Request&lt;br /&gt;Browser --&gt; Crunchy: Authentication Response&lt;br /&gt;note over Crunchy: Retrieves and processes file&lt;br /&gt;Crunchy -&gt; Browser: Sends processed file&lt;br /&gt;Browser --&gt; User: Displays file&lt;br /&gt;&lt;/pre&gt;In this last example using the syntax of &lt;a href="http://www.websequencediagrams.com/"&gt;this site&lt;/a&gt; which generates the following picture:&lt;br /&gt;&lt;img src="http://www.websequencediagrams.com/cgi-bin/cdraw?lz=VXNlciAtLT4gQnJvd3NlcjogY2xpY2tzIG9uIGEgbGluawoAEwcgLT4gQ3J1bmNoeTogcmVxdWVzdHMgZmlsZQoAEAcAOg5BdXRoZW50aWNhdGlvbiBSACwGAEQKAEMMABsRc3BvbnNlCm5vdGUgb3ZlcgBwClJldHJpZXZlcyBhbmQgcHJvY2Vzc2UAeRAAgUULU2VuZHMAIAlkAIEsBgB6DFUAgXIFRGlzcGxheQCBSwc&amp;amp;s=modern-blue" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;(Btw, the picture above is generated automatically each time this page is loaded - so it depends on the availability of the server.  I have used a static version of this picture in the documentation for Crunchy.)&lt;br /&gt;&lt;br /&gt;Using svg to do graphics is fairly easy.  Using svg as embedded objects in an html document requires a bit of searching on the internet.  Creating such documents and displaying dynamically requires even more searching (or perhaps more careful reading...).  The thing important to remember is to serve the document as "application/xhtml+xml" instead of the usual "text/html".   I thought it would be useful to share an almost minimal working example (tested only on Firefox) and perhaps save some time for others that would like to do the same.  Feel free to adapt it as you like.&lt;br /&gt;&lt;br /&gt;&lt;span style=";font-family:courier new;font-size:78%;"  &gt;svg_test = """&amp;lt;?xml version="1.0" encoding="UTF-8"?&gt;&lt;br /&gt;&amp;lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"&lt;br /&gt;   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&gt;&lt;br /&gt;&amp;lt;html xmlns="http://www.w3.org/1999/xhtml"&lt;br /&gt;   xmlns:svg="http://www.w3.org/2000/svg"&gt;&lt;br /&gt;&amp;lt;head&gt;&amp;lt;/head&gt;&lt;br /&gt;&amp;lt;body&gt;&lt;br /&gt; &amp;lt;h1&gt;SVG embedded inline in XHTML&amp;lt;/h1&gt;&lt;br /&gt; &amp;lt;svg:svg width="300px" height="200px"&gt;&lt;br /&gt;   &amp;lt;svg:circle cx="150px" cy="100px" r="50px" fill="%s"&lt;br /&gt;                          stroke="#000000" stroke-width="5px"/&gt;&lt;br /&gt; &amp;lt;/svg:svg&gt;&lt;br /&gt;&amp;lt;/body&gt;&lt;br /&gt;&amp;lt;/html&gt;&lt;br /&gt;"""&lt;/span&gt;&lt;br /&gt;&lt;pre&gt;&lt;svg:svg width="300px" height="200px"&gt;&lt;br /&gt;import BaseHTTPServer&lt;br /&gt;import webbrowser&lt;br /&gt;&lt;br /&gt;colors = ["#330000", "#660000", "#990000",  "#cc0000", "#ff0000"]&lt;br /&gt;index = 0&lt;br /&gt;class WebRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):&lt;br /&gt;def do_GET(self):&lt;br /&gt; global index&lt;br /&gt; self.send_response(200)&lt;br /&gt; self.send_header('Content-type', 'application/xhtml+xml')&lt;br /&gt; self.end_headers()&lt;br /&gt; self.wfile.write(svg_test% colors[index % 5])&lt;br /&gt; index += 1&lt;br /&gt;&lt;br /&gt;port = 8000&lt;br /&gt;server = BaseHTTPServer.HTTPServer(('',port), WebRequestHandler)&lt;br /&gt;webbrowser.open("http://127.0.0.1:%s"%port)&lt;br /&gt;server.serve_forever()&lt;br /&gt;&lt;/svg:svg&gt;&lt;/pre&gt;&lt;br /&gt;And, if you know how to suppress the output of the webserver, feel free to leave a comment.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-4199719444912289975?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/4199719444912289975/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=4199719444912289975' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/4199719444912289975'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/4199719444912289975'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/10/more-on-docpictures-and-almost-minimal.html' title='More on docpictures and (almost) minimal example of web server with inline svg'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-7502477300844911690</id><published>2008-10-14T22:43:00.002-03:00</published><updated>2008-10-14T22:50:57.631-03:00</updated><title type='text'>Viewing embedded pictures within docstrings</title><content type='html'>&lt;span style="font-size:100%;"&gt;In a recent post on the Python mailing list, it was suggested that it would be useful if "small" pictures could be embedded within docstrings as additional information.  As is often the case, many words were written ... but little code was produced.  As I have been guilty of this myself in the past, I decided that it was time to do things differently.  After a quick prototype sent to the Python list, I wrote this &lt;a href="http://code.activestate.com/recipes/576538/"&gt;recipe&lt;/a&gt; which gives a simple way to embed and display images inside a docstring in a totally transparent way.&lt;br /&gt;&lt;br /&gt;Is this something that people would find useful?  Any suggestions for improvements?  Should I implement this within Crunchy?  (Btw, Crunchy 1.0 alpha 1 has been released just last week).&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-7502477300844911690?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/7502477300844911690/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=7502477300844911690' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/7502477300844911690'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/7502477300844911690'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/10/viewing-embedded-pictures-within.html' title='Viewing embedded pictures within docstrings'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-1713445519454253208</id><published>2008-06-22T21:06:00.004-03:00</published><updated>2008-06-22T21:48:34.313-03:00</updated><title type='text'>Monkeypatching doctest</title><content type='html'>The Python &lt;a href="http://docs.python.org/lib/module-doctest.html"&gt;doctest&lt;/a&gt; module rocks.  Lately, I have been using it to write unit tests for Crunchy: for each module, I write a reStructuredText file which contains sample tests written as simulated interpreter sessions, using &lt;a href="http://docs.python.org/lib/doctest-basic-api.html"&gt;doctest.testfile()&lt;/a&gt;.  This has worked really well in general ... however, I have encountered one small annoyance, which I managed to get rid of in an "elegant" way using Monkeypatching.&lt;br /&gt;&lt;br /&gt;doctests allow the use of &lt;a href="http://docs.python.org/lib/doctest-options.html"&gt;directives&lt;/a&gt;.  One "powerful" directive is the ELLIPSIS directive. Quoting from the documentation:&lt;br /&gt;&lt;blockquote&gt;&lt;em&gt;When specified, an ellipsis marker (...) in the expected output    can match any substring in the actual output.  This includes    substrings that span line boundaries, and empty substrings, so it's    best to keep usage of this simple.  Complicated uses can lead to the    same kinds of "oops, it matched too much!" surprises that .*    is prone to in regular expressions. &lt;/em&gt;&lt;br /&gt;&lt;/blockquote&gt;Unfortunately, I encountered a case where the ellipsis marker did not allow enough matching! Consider the following situation: I have a program (Crunchy!) that saves the user's preferences (including the language) in a configuration file each time its value is changed.  It also gives some feedback to the user whenever this happens.&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:130%;"&gt;&lt;br /&gt;&amp;gt;&amp;gt;&amp;gt; &lt;span style="color: rgb(0, 102, 0);"&gt;original_value&lt;/span&gt; = &lt;span style="color: rgb(0, 102, 0);"&gt;crunchy.language&lt;/span&gt;&lt;br /&gt;&amp;gt;&amp;gt;&amp;gt; &lt;span style="color: rgb(0, 102, 0);"&gt;set_language&lt;/span&gt;('&lt;span style="color: rgb(153, 51, 0);"&gt;en&lt;/span&gt;')  # setting this value for some standardized tests&lt;br /&gt;Language has been set to English&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;At the end of the test, I want to restore the original value.&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:130%;"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;span style="color: rgb(0, 102, 0);"&gt;set_language&lt;/span&gt;(original_value) #doctest: +ELLIPSIS&lt;/span&gt;&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;Here I want the ellipsis (...) to match the string that is going to be printed out in the original language as I have no idea what this string will look like.  The problem is that the ellipsis in this case is thought to be a Python (continuation) prompt and not a string that is "matched".  One workaround that I had been using was to modify set_language to add a parameter ("verbose") that was set to True by default but that I could turn off when running tests.  While this is simple enough that it surely would &lt;i&gt;&lt;b&gt;never (!)&lt;/b&gt;&lt;/i&gt; introduce spurious bugs, it does not feel right; one should not modify functions only for the purpose of making them satisfy unit tests.&lt;br /&gt;&lt;br /&gt;According to the documentation,&lt;br /&gt;&lt;blockquote&gt;&lt;b&gt;register_optionflag&lt;/b&gt;(name)&lt;br /&gt;&lt;br /&gt;Create a new option flag with a given name, and return the new  flag's integer value.  register_optionflag() can be used when subclassing OutputChecker or  DocTestRunner to create new options that are supported by  your subclasses.   register_optionflag should always be  called using the following idiom:  &lt;/blockquote&gt; &lt;pre&gt;&lt;span style="font-size:130%;"&gt;  MY_FLAG = register_optionflag('MY_FLAG')&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This is great ... except that I want to used doctest.testfile() which does not allow me to specify a subclass of OutputChecker to use instead of the default. Also, I wanted to use as much of possible of the existing doctest module, with as little new code as possible.&lt;br /&gt;&lt;br /&gt;This is where monkeypatching comes in.&lt;br /&gt;&lt;br /&gt;After a bit of work, I came up with the following solution:&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:130%;"&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 0, 153);"&gt;from&lt;/span&gt; doctest &lt;span style="color: rgb(0, 0, 153);"&gt;import&lt;/span&gt; OutputChecker&lt;br /&gt;original_check_output = OutputChecker.check_output&lt;br /&gt;&lt;span style="color: rgb(0, 0, 153);"&gt;import&lt;/span&gt; doctest&lt;br /&gt;&lt;br /&gt;IGNORE_ERROR = doctest.register_optionflag("IGNORE_ERROR")&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 0, 153);"&gt;class&lt;/span&gt; MyOutputChecker(doctest.OutputChecker):&lt;br /&gt;&lt;span style="color: rgb(0, 0, 153);"&gt;    def&lt;/span&gt; check_output(self, want, got, optionflags):&lt;br /&gt; &lt;span style="color: rgb(0, 0, 153);"&gt;      if&lt;/span&gt; optionflags &amp;amp; IGNORE_ERROR:&lt;br /&gt;           &lt;span style="color: rgb(0, 0, 153);"&gt;return True&lt;/span&gt;&lt;br /&gt; &lt;span style="color: rgb(0, 0, 153);"&gt;      return&lt;/span&gt; original_check_output(self, want, got, optionflags)&lt;br /&gt;&lt;br /&gt;doctest.OutputChecker = MyOutputChecker&lt;br /&gt;&lt;br /&gt;failure, nb_tests = doctest.testfile("test_doctest.rst")&lt;br /&gt;&lt;span style="color: rgb(0, 0, 153);"&gt;print&lt;/span&gt; "%d failures in %d tests" % (failure, nb_tests)&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;And here's the content of test_doctest.rst&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:130%;"&gt;&lt;br /&gt;Test of the new flag:&lt;br /&gt;&lt;br /&gt;&gt;&gt;&gt; print 42&lt;br /&gt;42&lt;br /&gt;&gt;&gt;&gt; print 2 # doctest: +IGNORE_ERROR&lt;br /&gt;SPAM!&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This yields a test with no failures. There might be a more elegant way of doing this; if so, I would be very interested in hearing about it.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-1713445519454253208?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/1713445519454253208/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=1713445519454253208' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/1713445519454253208'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/1713445519454253208'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/06/monkeypatching-doctest.html' title='Monkeypatching doctest'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-6180625941275341269</id><published>2008-04-18T21:29:00.001-03:00</published><updated>2008-04-18T21:29:19.302-03:00</updated><title type='text'>Thoughts on Google Summer of Code 2008 - part 1</title><content type='html'>&lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;In just a few days, Google will make some announcements that will please many hundreds of students and disappoint even more. I think we should all focus on the positive side when the announcements are made.  It is, after all, a fantastic thing that a company is spending millions of dollars so that some students get the chance to program on Open Source software as a summer job.&lt;br/&gt;&lt;br/&gt;Just think of it.  Who could have predicted, just five years ago, that a company would spend that kind of money on students who would work on someone else's project?&lt;br/&gt;&lt;br/&gt;This is amazing - and many are now taking it for granted.&lt;br/&gt;&lt;br/&gt;I find it great that the Python Software Foundation is an organization that can mentor SoC students.  With the excellent supporting work of James Tauber as coordinator, many promising students are going to be paired with a mentor, hopefully leading to great projects to be completed this summer.&lt;br/&gt;&lt;br/&gt;I have seen some grumblings on some SoC related lists that have made me thought about some of the "problems" I have seen.  &lt;b&gt;Note that these are very minor compared with the strong positive points.&lt;/b&gt; I will be discussing those in part 2, after the official announcements are made.&lt;br/&gt;&lt;br/&gt;&lt;ul&gt;&lt;li&gt;Because the PSF is an umbrella organization, most students work on different projects, unrelated with each other.  As a result, they tend to have limited interactions with the greater Python community.  I think there should be a "meeting place" where all the students would meet - perhaps a mailing list to which they have all to contribute once a week, sharing their progress, etc.&lt;/li&gt;&lt;li&gt;Not enough positive publicity is given to "successful students", i.e. those that continue to contribute to the Python community after the summer is over.   For Crunchy, it has been one success (Johannes Woolard) out of a total of 3 students over the past 2 years. I don't know of many other success stories from other projects ...  Alex Holkner comes to mind ... but I feel I should know more names of successful students.  (I know there's another Alex or Alessandro who has contributed to the Python core and was involved with GHOP....)&lt;br/&gt;&lt;/li&gt;&lt;li&gt;With the exception of a few people like James Tauber and Titus Brown with whom I have had a few email exchanges, I do not feel that as a past/potential mentor I am as much part of a community as I feel should be the case.   There is a mentor discussion list, but it does not seem to be the kind of place to generate community building discussions.&lt;br/&gt;&lt;/li&gt;&lt;/ul&gt;In terms of projects submitted, I would describe them to belong in the following categories:&lt;br/&gt;&lt;br/&gt;1. Contributions to the "standard" core (cpython code, or standard library)&lt;br/&gt;&lt;br/&gt;2 a. Contributions to "non-standard core"  (e.g. Jython, PyPy, TinyPy?)&lt;br/&gt;2 b. Contributions to 3rd party libraries (e.g. Numpy, Pygame)&lt;br/&gt;2 c. Contributions to major projects whose end users have to use Python (e.g. SAGE)&lt;br/&gt;2 d. Contributions to projects that can be used to teach Python  [Crunchy, of course ;-), but there are others ... that will be for part 2]&lt;br/&gt;&lt;br/&gt;3 a. Contributions that propose some new "standards" for Python programmers, never discussed before in the Python community.&lt;br/&gt;3 b. Projects that happen to be written in Python, but whose end users are not exposed (or minimally exposed) to Python.&lt;br/&gt;3 c. Projects that are not written in Python, that may or may not be usable in all OS, and that aren't more useful to Python programmers than they would be to people using other languages.&lt;br/&gt;3 d. &lt;i&gt;et cetera&lt;/i&gt;&lt;br/&gt;&lt;br/&gt;Assuming that all projects are well-thought of  (which is not always the case), I feel that:&lt;br/&gt;&lt;br/&gt;&lt;ul&gt;&lt;li&gt;Projects in category 1 deserve to be fully supported.  The Python community need more capable people contributing to the core to prevent burnout for the current contributors.  Perhaps, in a few years, after working some more on Crunchy, I'll feel capable of joining that group and contributing effectively (and have the time to do so).&lt;/li&gt;&lt;li&gt;Projects in categories 2 a-d are worthy of support.  There are of course more such projects submitted than can possibly be supported, so some difficult choices had to be made.  (Kudos to James for guiding this process.)  Many people are going to be disappointed, but this was unavoidable.&lt;br/&gt;&lt;/li&gt;&lt;li&gt;Projects in categories 3 a-d are a puzzle to me.  I don't understand their appeal for the PSF (and I know I am not the only one), but it seems that very few people are willing to take a public stance on this and debate the issue.  &lt;b&gt;Note that this comment is made as an observation on the discussions that took place so far and does not necessarily reflect on any decision that has been made.&lt;/b&gt;&lt;br/&gt;&lt;/li&gt;&lt;/ul&gt;This is it for the negative comments.  I can't wait for the announcements from Google to focus entirely on the more positive side.&lt;br/&gt;&lt;span class='nfakPe'/&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-6180625941275341269?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/6180625941275341269/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=6180625941275341269' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/6180625941275341269'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/6180625941275341269'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/04/thoughts-on-google-summer-of-code-2008.html' title='Thoughts on Google Summer of Code 2008 - part 1'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-2983430142861880117</id><published>2008-04-13T17:59:00.005-03:00</published><updated>2008-04-15T07:22:00.348-03:00</updated><title type='text'>Firefox 3b5: the pain of using the bleeding edge</title><content type='html'>&lt;div xmlns="http://www.w3.org/1999/xhtml"&gt;After seeing so many positive reviews of the upcoming Firefox 3, I decided to try the latest beta (5) version.  It seems indeed to be &lt;i&gt;fast&lt;/i&gt; when dealing with complex javascript.  While there are a few features I am not too keen about [1], I liked the extra speed (and the reduced RAM usage) so much that I have been using it almost exclusively.  That is until now, since I can't rely on it to test Crunchy.&lt;span style="color: rgb(255, 0, 0);"&gt; &lt;span style="font-weight: bold;"&gt;&lt;span style="color: rgb(0, 102, 0);"&gt;Update: this is no longer true, thanks to a reader's comment.  The fix was to move the &lt;span style="color: rgb(255, 0, 0);"&gt;onblur&lt;/span&gt; event to the file input, indicated by&lt;/span&gt; HERE.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;To load a local html file [2] into Crunchy, a two-step process has to be used due to normal javascript security:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;form name="browser_local"&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;onblur&lt;/span&gt;="document.submit_local.url.value =&lt;br /&gt;    document.browser_local.filename.value"&amp;gt;&lt;br /&gt;&amp;lt;input name="filename"  type="file" &lt;span style="color: rgb(204, 0, 0);font-size:80;" &gt;&lt;span style="font-size:100%;"&gt;HERE &lt;/span&gt;&lt;/span&gt;&amp;gt;&lt;br /&gt;&amp;lt;/form&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;form action="/local" method="get" name="submit_local"&amp;gt;&lt;br /&gt; &amp;lt;input name="url" type="hidden"&amp;gt;&lt;br /&gt; &amp;lt;input class="crunchy" type="submit"&amp;gt;&lt;br /&gt;        value="Load local html tutorial" /&amp;gt;&lt;br /&gt;&amp;lt;/form&amp;gt;&lt;br /&gt;&lt;/pre&gt;The first form allows to browse the local drive for a particular file.  The second one sends the chosen file's path to the browser as an argument to the "/local" action, something like &lt;span style="font-family:Courier New;"&gt;/local?url=file_path&lt;/span&gt;.   Unfortunately, when using Firefox 3 beta 5, no argument is passed and we get &lt;span style="font-family:Courier New;"&gt;/local?url=&lt;/span&gt; instead.  And of course no file can be loaded.&lt;br /&gt;&lt;br /&gt;This file browser feature is not something I test regularly when working on Crunchy, nor is it something that can be tested via standard Python unit tests.  [3] When I noticed the new bug, it never crossed my mind that this could be a "new Firefox feature" and thought it was something I had broken in Crunchy's code. [4]  It was only after I tried a few old releases of Crunchy (to figure out &lt;i&gt;when&lt;/i&gt; "I" broke the code) that I figured out that the problem was not due to anything I wrote.&lt;br /&gt;&lt;br /&gt;I have not been able to find any note about this new behavior of Firefox.  Since this is still a beta, I guess I'll have to wait until the final Firefox 3 release to figure out if I need to change the way I load files. [5]&lt;br /&gt;&lt;br /&gt;====&lt;br /&gt;[1] One change I don't like is the rather gaudy auto-suggest list when typing a url.&lt;br /&gt;&lt;br /&gt;[2] The same method is used to load reStructuredText files and others.&lt;br /&gt;&lt;br /&gt;[3] I really need to investigate twill for this.&lt;br /&gt;&lt;br /&gt;[4] One more reason to have a complete unit test coverage.  Since I don't, I automatically assumed it was something I had done.&lt;br /&gt;&lt;br /&gt;[5] If anyone has any lead as to how to do so reliably in Firefox 3b5 as well as with other browsers, I'd be keen to hear about it.&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-2983430142861880117?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/2983430142861880117/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=2983430142861880117' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/2983430142861880117'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/2983430142861880117'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/04/firefox-3b5-pain-of-using-bleeding-edge.html' title='Firefox 3b5: the pain of using the bleeding edge'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-7400387089589260381</id><published>2008-04-11T18:38:00.002-03:00</published><updated>2008-04-11T18:40:55.101-03:00</updated><title type='text'>Shell meme</title><content type='html'>I'm responding to peer pressure.   I pretty much only use the shell for one thing and rarely restart it...&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;andre$ history|awk '{a[$2]++ } END{for(i in a){print a[i] " " i}}'|sort -rn|head&lt;br /&gt;322 python&lt;br /&gt;76 cd&lt;br /&gt;26 ls&lt;br /&gt;11 grep&lt;br /&gt;8 pwd&lt;br /&gt;8 find&lt;br /&gt;5 sudo&lt;br /&gt;3 rm&lt;br /&gt;3 def&lt;br /&gt;2 svn&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-7400387089589260381?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/7400387089589260381/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=7400387089589260381' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/7400387089589260381'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/7400387089589260381'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/04/shell-meme.html' title='Shell meme'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-331882857375178987</id><published>2008-03-22T19:54:00.004-03:00</published><updated>2008-03-22T21:56:50.975-03:00</updated><title type='text'>Using Clone Digger</title><content type='html'>There's a new tool available for Python programmers: &lt;a href="http://clonedigger.sourceforge.net/"&gt;Clone Digger&lt;/a&gt;. While it has not been officially released, it is available from the svn repository.  Clone Digger finds code duplications in a given project, and creates a fairly comprehensive report (html file).  Seeing the duplications on a screen is a powerful motivation for refactoring.&lt;br /&gt;&lt;br /&gt;Check it out!&lt;br /&gt;&lt;br /&gt;Update:&lt;br /&gt;&lt;br /&gt;Just to make it clear: I had nothing to do with this project; I just found out about it via the gsoc-python-mentors list.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-331882857375178987?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/331882857375178987/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=331882857375178987' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/331882857375178987'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/331882857375178987'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/03/using-clonedigger.html' title='Using Clone Digger'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-5197403592232964850</id><published>2008-03-22T11:43:00.003-03:00</published><updated>2008-03-22T13:45:34.873-03:00</updated><title type='text'>Inspiration and persistence</title><content type='html'>While &lt;strike&gt;mindlessly wasting time instead of programming&lt;/strike&gt; &lt;span style="font-style: italic;"&gt;selectively reading&lt;/span&gt; the internet, I came across &lt;a href="http://sethgodin.typepad.com/seths_blog/2008/03/persistence.html"&gt;this gem by Seth Godin&lt;/a&gt; which I reproduce in its entirety:&lt;br /&gt;&lt;blockquote style="color: rgb(102, 51, 102);"&gt;&lt;h3 class="entry-header"&gt;Persistence&lt;/h3&gt;       &lt;div class="entry-body"&gt;    &lt;p&gt;Persistence isn't using the same tactics over and over. That's just annoying.&lt;/p&gt;  &lt;p&gt;Persistence is having the same goal over and over.&lt;/p&gt;&lt;/div&gt;&lt;/blockquote&gt;&lt;p style="font-weight: bold;"&gt;That's it.&lt;/p&gt;&lt;p&gt;A wiser person would most likely leave it at that.  However, this lead me thinking of my goals when it comes to programming which I thought I should write down if only to help me reflect upon them again at a later time.  I can sum them up as follows:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Do something that is fun but that gives me some sense of accomplishment rather than just wasting time.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Find ways to make it easier for others to learn programming (in Python).&lt;/li&gt;&lt;/ol&gt;In doing so, I have found myself oscillating between two extremes:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Trying to follow the "&lt;span style="font-style: italic;"&gt;release early, release often&lt;/span&gt;" philosophy.&lt;/li&gt;&lt;li&gt;Trying to get everything "&lt;span style="font-style: italic;"&gt;just perfect&lt;/span&gt;" before releasing anything.&lt;br /&gt;&lt;/li&gt;&lt;/ol&gt;Trying to get things "just perfect" is something that can lead to procrastination and delays.  As an example, &lt;a href="http://rur-ple.sourceforge.net/"&gt;rur-ple&lt;/a&gt;'s version 1.0 release candidate 3  has not been updated since July 2007.  The next version should be the final 1.0 ... but somehow, I am not happy with many details and I'd like to get everything right for 1.0.  Too often I read about (usually commercial) software which is officially released and is considered by its users to be a Beta version.  All open source programmers I have met have a sense of pride in their work that I share.  So I postpone the final release and end up working on something else...&lt;br /&gt;&lt;br /&gt;I went the other way with a little utility called &lt;a href="http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/473812"&gt;lightning compiler&lt;/a&gt; (now at &lt;a href="https://sourceforge.net/project/showfiles.php?group_id=125834"&gt;version 2.1&lt;/a&gt;), whose version 1.0 was released as a recipe in the online &lt;a href="http://aspn.activestate.com/ASPN/Cookbook/Python"&gt;Python cookbook&lt;/a&gt;.  Much of the rapid evolution of lightning compiler came from user feedback, as expected from the "release early, release often" philosophy.  Yet, following the same philosophy generated relatively little feedback for &lt;a href="http://rur-ple.sourceforge.net/"&gt;rur-ple&lt;/a&gt; of for &lt;a href="http://code.google.com/p/crunchy/"&gt;crunchy&lt;/a&gt;  to date. I did get some feedback for rur-ple which has been used at an &lt;a href="http://showmedo.com/videos/series?name=pythonJensFromKidsSeries"&gt;elementary school in Austria&lt;/a&gt;, at a &lt;a href="http://www.mail-archive.com/edu-sig@python.org/msg01604.html"&gt;high school&lt;/a&gt; and a &lt;a href="https://www.cs.indiana.edu/classes/a201-hayn/PersonalMachines.html"&gt;university&lt;/a&gt; in the U.S., among others, but it has often been very indirect.&lt;br /&gt;&lt;br /&gt;Still, I am persistent.  Following Seth Godin's definition of persistence, my second goal written above can be described as finding a solution as to &lt;a href="http://www.salon.com/tech/feature/2006/09/14/basic/"&gt;Why Johnny can't code&lt;/a&gt;.  Or, as I have &lt;a href="http://osdir.com/ml/python.education/2006-06/msg00071.html"&gt;written elsewhere &lt;/a&gt;&lt;br /&gt;&lt;blockquote&gt;M&lt;span style="font-style: italic;"&gt;y goal is to provide an introduction to programming which is as "smooth" as possible.  We sometime hear the phrase "steep learning curve" to characterize some difficult to grasp concept.  I think it is important to have as few "steep learning curves" as possible in the learning process.   GvR &lt;/span&gt;&lt;a href="http://gvr.sourceforge.net/"&gt;[Guido van Robot]&lt;/a&gt;&lt;span style="font-style: italic;"&gt; uses a slightly easier syntax than Python ... but at the expense of having a "step-like learning curve" when one wants to go from GvR's world to Python programming.  Since Rur-ple uses Python, there is no transition to speak of&lt;/span&gt;.&lt;/blockquote&gt;Both rur-ple and Crunchy, and to a lesser extent lightning compiler (which has been incorporated within rur-ple) have been inspired by that goal.&lt;br /&gt;&lt;br /&gt;However, sometimes I stray from that goal.  For example, inspired by an earlier post on Georg Brand's remarkable &lt;a href="http://sphinx.pocoo.org/"&gt;Sphinx&lt;/a&gt;, Crunchy now includes a prototype for an automated documentation testing framework along the lines of &lt;a href="http://sphinx.pocoo.org/ext/doctest.html"&gt;sphinx.ext.doctest&lt;/a&gt; which was released yesterday.  My intention is to update Crunchy's implementation so that it can be totally compatible with Sphinx's.  And while I believe that this is a neat (and fun!) thing to include in Crunchy, it only very indirectly contribute to my overall goal and ends up delaying the 1.0 release for Crunchy.&lt;br /&gt;&lt;br /&gt;Blogging too can be a distraction.  However, it is my hope that it may generate a few comments that will contribute to inspire me to make Crunchy even more useful.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;Success is the result of inspiration and persistence.&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-5197403592232964850?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/5197403592232964850/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=5197403592232964850' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/5197403592232964850'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/5197403592232964850'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/03/inspiration-and-persistence.html' title='Inspiration and persistence'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-8130414994820430529</id><published>2008-03-07T21:47:00.002-04:00</published><updated>2008-03-07T21:58:46.012-04:00</updated><title type='text'>Crunchy: Pycon 2008 release</title><content type='html'>Crunchy is getting really close to a 1.0 version.  To mark the Pycon 2008 event (that I won't be able to attend), I just did a new release (0.9.9).  It has a few new goodies that I won't list here, leaving Johannes do the demonstration.  As for me, I am heading down South for a vacation with my kids.&lt;br /&gt;&lt;br /&gt;Note: the opening Crunchy page indicates that this is version 0.9.8.6 - which is incorrect.&lt;br /&gt;&lt;br /&gt;What is left to be done for version 1.0 is cleaning up the existing documentation (proofreading, proofreading, proofreading) and adding a few more pages to it.  New features will have to wait until after 1.0.... unless we get feedback from Pycon attendees for "must have" features that we could implement quickly.&lt;br /&gt;&lt;br /&gt;As far as I know, there are no bugs (famous last words).  If you find any, please let us know.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-8130414994820430529?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/8130414994820430529/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=8130414994820430529' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/8130414994820430529'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/8130414994820430529'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/03/crunchy-pycon-2008-release.html' title='Crunchy: Pycon 2008 release'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-8512727337131004549</id><published>2008-02-29T23:09:00.002-04:00</published><updated>2008-02-29T23:20:25.275-04:00</updated><title type='text'>Pycon and Crunchy</title><content type='html'>This year's Pycon program looks very interesting. I wish I could be there but, alas, the timing was just wrong for me this year.  This is doubly disappointing as I would have been able to meet with Johannes Woolard in the flesh.  Yes, forget Guido van Rossum, Alex Martelli and other famous names: the one person I wanted to meet is Johannes.  For more than a year an a half, I have had the pleasure of collaborating with Johannes on Crunchy, without ever meeting him.  This year, Johannes will be the one showing Crunchy off.   I'm sure he'll do a great job.&lt;br /&gt;&lt;br /&gt;And, if anyone is looking to hire a bright, young, hard-working programmer, Johannes will graduate from Oxford this year.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-8512727337131004549?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/8512727337131004549/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=8512727337131004549' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/8512727337131004549'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/8512727337131004549'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/02/pycon-and-crunchy.html' title='Pycon and Crunchy'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-8868082559689826064</id><published>2008-02-22T20:26:00.002-04:00</published><updated>2008-02-22T20:46:10.480-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='crunchy'/><title type='text'>99 problems: looking for volunteers</title><content type='html'>Some time ago, Dr. Werner Hett created a &lt;a href="https://prof.ti.bfh.ch/hew1/informatik3/prolog/p-99/"&gt;list of 99 Prolog problems&lt;/a&gt; that could be used to skills in logic programming.  More recently, a Ruby learner posted a &lt;a href="http://curiouscoding.wordpress.com/2008/02/11/learning-ruby-with-ninety-nine-problems-1-10/"&gt;Ruby version of the first 10 problems&lt;/a&gt;, and his solutions.   This seemed to be a good idea, especially if one makes use of doctests ... and &lt;a href="http://code.google.com/p/crunchy/"&gt;Crunchy&lt;/a&gt; :-).  So, I've started my own version of these which you can get as a zip file (containing 6 problems and their solutions) from the &lt;a href="http://code.google.com/p/crunchy/"&gt;Crunchy main page&lt;/a&gt;.  If you have never done so before, to load a local html file within Crunchy, you simply click on the "Browsing" menu on the left hand side and scroll down until you reach the "Closer to home" section and follow the instructions.&lt;br /&gt;&lt;br /&gt;Note that with the next version of Crunchy (the current one is 0.9.8.6) you will be able to start Crunchy with an arbitrary file using something like&lt;br /&gt;&lt;br /&gt;python crunchy.py --url=full_local_path_or_url&lt;br /&gt;&lt;br /&gt;It would be nice if there could be a complete Python version of the 99 Prolog problems.  If anyone is interested in helping, please do not hesitate to contact me.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-8868082559689826064?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/8868082559689826064/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=8868082559689826064' title='22 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/8868082559689826064'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/8868082559689826064'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/02/99-problems-looking-for-volunteers.html' title='99 problems: looking for volunteers'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>22</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-3863341971940507054</id><published>2008-02-01T19:43:00.000-04:00</published><updated>2008-02-01T19:54:44.513-04:00</updated><title type='text'>Automated documentation code testing - part 2</title><content type='html'>Thanks to Crunchy's simple plugin architecture, after only a few hours of coding the automated documentation testing (for html files) &lt;a href="http://aroberge.blogspot.com/2008/01/automated-documentation-code-testing.html"&gt;described here&lt;/a&gt; has been implemented (the "first approach" described, that is.)  It will be part of the next Crunchy release.  In theory, code samples for a complete book could be all tested at the click of a button, provided that the book is available as an html document.   The next step will be to define a few new directives so that reStructuredText documents can be used as well.&lt;br /&gt;&lt;br /&gt;Now, while I have a few sample test files, it would be nice is to find someone who has a real life document with embedded Python code samples as a test user...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-3863341971940507054?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/3863341971940507054/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=3863341971940507054' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/3863341971940507054'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/3863341971940507054'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/02/automated-documentation-code-testing.html' title='Automated documentation code testing - part 2'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-202455290431238091</id><published>2008-01-29T18:29:00.000-04:00</published><updated>2008-01-29T20:52:27.519-04:00</updated><title type='text'>Automated documentation code testing</title><content type='html'>During the first year or so of development work on &lt;a href="http://code.google.com/p/crunchy"&gt;Crunchy&lt;/a&gt;, I probably got a nickname of "Dr. NO!" by early Crunchy adopters as I often resisted suggestions for adding new capabilities.   At the time, Crunchy required that html pages have additional markup added  (&lt;span style="font-style: italic;"&gt;vlam = very little additional markup&lt;/span&gt;) so that Crunchy could process them properly.  I wanted Crunchy-enabled tutorials to be very easily created, without much additional work from tutorial writers, so that Crunchy would be adopted by many people.  Most of the suggestions that were made, including some by Johannes, both while he was sponsored by Google as a Summer of Code student and afterwards when he became a co-developer, were rejected by me for that reason.  Since then, the situation has changed, mainly for two reasons:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Johannes created a new basic infrastructure for Crunchy where we can introduce new capabilities via plugins, without needing to change a single line of the core in most instances.&lt;/li&gt;&lt;li&gt;Based on the new architecture, I came up with a new way to process pages so that no additional markup was needed for Crunchy to do its magic.  This is what makes it possible, for example, to interact with the official Python tutorial on the &lt;a href="http://www.python.org/"&gt;python.org&lt;/a&gt; site.&lt;br /&gt;&lt;/li&gt;&lt;/ol&gt;Now, that it is so easy to implement new capabilities, I am revisiting some ideas I had rejected or ignored before.  The struggle I have is to decide when &lt;span style="font-style: italic;"&gt;enough is enough&lt;/span&gt;  before finally having a version 1.0 officially released.&lt;br /&gt;&lt;br /&gt;In any event, after reading some comments on &lt;a href="http://pyside.blogspot.com/2008/01/what-do-you-look-for-in-documentation.html"&gt;this post&lt;/a&gt; by Georg Brandl, I started thinking about adding a new option to test code embedded in documentation.  To quote from the comments on that post:&lt;br /&gt;&lt;br /&gt;&lt;blockquote style="font-style: italic;"&gt;One thing that is seriously needed is the ability to run and test code snippets in some fashion. It's just too easy for documentation to get out of date relative to the code, and if you can effectively "unit test" your docs, you're in much better shape.&lt;br /&gt;&lt;br /&gt;And I don't mean like doctests, because not everything lends it self well to that style of testing. If it's possible to mark up some code as being for test fixture and some code as being what belongs in the doc, that would be good.&lt;/blockquote&gt;Alternatively, from another reader:&lt;br /&gt;&lt;br /&gt;&lt;blockquote style="font-style: italic;"&gt;For me a key is being able to test code in the docs, and think the key is being able to "annotate" a code snipit with information about the context in which it should run, and the output it should give.&lt;/blockquote&gt;&lt;br /&gt;I think that Crunchy is a very good platform to implement this.  There are currently three complementary options I am considering, one of which I have started to implement.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The first option is to have something like the following [&lt;span style="font-size:78%;"&gt;note that while I use html notation, Crunchy is now capable of handling reStructuredText, including having the possibility of dealing with additional directives&lt;/span&gt;]:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic; color: rgb(204, 0, 0);"&gt;Some normally hidden code, used for setup:&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(204, 0, 0);"&gt;&amp;lt;pre title="setup_code name=first"&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(204, 0, 0);"&gt;a=42&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(204, 0, 0);"&gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic; color: rgb(0, 0, 153);"&gt;Followed by the code sample to be tested:&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 0, 153);"&gt;&amp;lt;pre title="check_code name=first"&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 0, 153);"&gt;print a &lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 0, 153);"&gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic; color: rgb(0, 102, 0);"&gt;And the expected output:&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 102, 0);"&gt;&amp;lt;pre title="code_output name=first"&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 102, 0);"&gt;42&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 102, 0);"&gt;&amp;lt;/pre&amp;gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 0, 0);"&gt;Upon importing a document containing such examples, Crunchy would insert a button for each code sample allowing the user to test the code by clicking on the button, invoking the appropriate setup, and comparing with the expected output.  Alternatively, all such code samples in a document could be run by a single click on a button inserted at the top of a page.  A javascript alert could be used to inform the user that all tests passed - otherwise error messages could be inserted in the page indicating which tests failed or passed.&lt;br /&gt;&lt;br /&gt;This type of approach could, in theory, be used for other languages than Python; code could be executed by passing information to a separate process launched in a terminal window, with the result fed back into Crunchy as described above.&lt;br /&gt;&lt;br /&gt;A second approach is to use the same method used by doctest to combine code sample and expected output; the setup code could still be used as described above.&lt;br /&gt;&lt;br /&gt;A third approach, this one completely different, could be used for more general situation than simply for documentation code testing.&lt;br /&gt;&lt;br /&gt;Currently, the Python code needs to be embedded inside an html (or rst) document.  However, one could create links to code that lives inside separate Python files.  For example, one could have the following:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;pre title="python_file"&amp;gt;&lt;br /&gt;&amp;lt;span title="python_file_name"&amp;gt; file_path &amp;lt;/span&amp;gt;&lt;br /&gt;&amp;lt;span title="python_file_linenumbers"&amp;gt; some_range &amp;lt;/span&amp;gt;&lt;br /&gt;&amp;lt;/pre&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;When viewing the above using a normal browser, one would see something like (using  a fictitious example)&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;../crunchy_dir/crunchy.py&lt;br /&gt;[1-3, 5, 7, 10-15]&lt;br /&gt;&lt;/pre&gt;However, when viewing the same page with Crunchy, the appropriate lines would be extracted from the file and displayed in the browser.  Alternatively, instead of specifying the line numbers, one could have a directive to extract a specific function/method/class as in&lt;br /&gt;&lt;br /&gt;&amp;lt;span title="python_file_function"&amp;gt; function_name &amp;lt;/span&amp;gt;&lt;br /&gt;&lt;br /&gt;which would instruct Crunchy to extract all the code for the function definition, and inserting it in the document.  By using such links, the code in the documentation would always (by definition) be kept in sync with the real code.   I realize that this is not exactly a novel idea but one whose potential could be extended by using Crunchy in ways never seen before.  However, this last approach will have to wait until after Crunchy version 1.0 has been released.&lt;br /&gt;&lt;br /&gt;What do you think of these ideas?&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-202455290431238091?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/202455290431238091/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=202455290431238091' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/202455290431238091'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/202455290431238091'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/01/automated-documentation-code-testing.html' title='Automated documentation code testing'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-295340211968414753</id><published>2008-01-28T20:23:00.001-04:00</published><updated>2008-01-28T20:42:55.073-04:00</updated><title type='text'>Kudos to ActiveState</title><content type='html'>&lt;a href="http://activestate.com/"&gt;ActiveState&lt;/a&gt;, the company behind the &lt;a href="http://aspn.activestate.com/ASPN/Cookbook/Python"&gt;Python cookbook&lt;/a&gt; and many other useful and free resources, is a great supporter of free software.  Its free Komodo Edit is a nice piece of software, well worth trying.&lt;br /&gt;&lt;br /&gt;I started using &lt;a href="http://activestate.com/Products/komodo_edit/"&gt;Komodo Edit&lt;/a&gt; a few months after I switched from using Windows to using a Mac.  On Windows, my Python editor of choice was SPE.  However, I found that SPE, not being a native Mac application, had some small quirks that I found annoying.  After trying Textmate, praised by many Mac users, and Wing among others, I settled on the free Komodo Edit.  While I missed the class browser I had gotten used to with SPE, I found that Komodo was enough for my basic needs.&lt;br /&gt;&lt;br /&gt;After reading &lt;a href="http://pyjesse.disqus.com/do_you_use_an_python_code_analyzer/"&gt;this post &lt;/a&gt;by Jesse Noller, I started using pylint within Komodo and while I did not find any bugs (so far!) in my code, it did encourage me to improve the existing code.  The possibility of easily adding new tools to Komodo Edit lead me to try its more powerful sibling, &lt;a href="http://www.activestate.com/Products/komodo_ide/"&gt;Komodo IDE&lt;/a&gt;. Komodo IDE has an integrated debugger (something I had *never* used before for code development but that I will likely use more and more in the future)  and a code browser side bar which is even better than the one included with SPE.  After using it for about a week, I decided to treat myself and purchase a license for it before the trial license ended. However, since I found the price a bit steep for something to use just for fun, I inquired about available discounts.  I was told that, even though I did not use it for my work, I was eligible for an educational discount given that I work at a University.&lt;br /&gt;&lt;br /&gt;However, there was more to come...&lt;br /&gt;&lt;br /&gt;When I indicated that I intended to buy it online, I got an email telling me that I was actually eligible for a deeper discount since I had a license for an earlier version of Komodo Personal edition.  This was a total surprise for me. Here's what happened: more than two and a half year ago, ActiveState had a special promotion for open source developers to get a free license for Komodo Personal edition. I had taken advantage of this offer at the time and installed Komodo on my Windows computer.  However, I found it was comparable in functionality to SPE for which I had a slight preference.  As a result, I gave up on Komodo after trying it for about a week.&lt;br /&gt;&lt;br /&gt;Now, more than 2.5 years later, the friendly people at ActiveState reminded me that I had a valid (but free!) license and told me I could simply pay for an upgrade to what is in my opinion a much superior programming environment than the version I had a license for.&lt;br /&gt;&lt;br /&gt;Talk about friendly customer service!  Thank you ActiveState!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-295340211968414753?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/295340211968414753/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=295340211968414753' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/295340211968414753'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/295340211968414753'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/01/kudos-to-activestate.html' title='Kudos to ActiveState'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-938767466982977883</id><published>2008-01-23T22:58:00.000-04:00</published><updated>2008-01-24T07:32:50.629-04:00</updated><title type='text'>GHOP Python Related Success Story</title><content type='html'>Early on during Google's HOP contest, I joined the Python mentors group following a call for task suggestions, and submitted many Crunchy-related tasks.  I was amazed by the quality of some contributions.  As more potential mentors involved with other Python related projects joined in, I decided to quietly refrain from submitting more Crunchy related tasks.  Still, Crunchy got more than its share of contributions from students from all over the world.&lt;br /&gt;&lt;br /&gt;Today, one student posted a blog entry about a &lt;a href="http://pythonbytes.blogspot.com/2008/01/success-crunchy-presentation.html"&gt;Crunchy presentation&lt;/a&gt; he made to his class.  He describes it as a success - and I would agree.  However, it is clear to me that the success of his presentation is due by far more to Python's strength than to Crunchy itself.  I thought it was a very good example to use when advocating for the use of Python - and therefore, worth linking to.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-938767466982977883?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/938767466982977883/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=938767466982977883' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/938767466982977883'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/938767466982977883'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/01/ghop-python-related-success-story.html' title='GHOP Python Related Success Story'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-9176412109266498324</id><published>2008-01-19T23:24:00.000-04:00</published><updated>2008-01-20T00:32:49.192-04:00</updated><title type='text'>More power and removing an option</title><content type='html'>One of the neat features of Crunchy, suggested to me by Andrew Dalke at Pycon 2007, is its ability to dynamically display images that are generated by some Python code.  The example given in the Crunchy distribution uses matplotlib.  However, the way this is done is slightly cumbersome.  First, some pre-existing vlam (&lt;span style="font-style: italic;"&gt;very little added markup&lt;/span&gt;) must already have been added to an html page, specifying the name of the image (e.g. foo.png) to be displayed.  This special vlam will result in an editor appearing on the page together with two buttons (one to execute the code, the other to load the image). Second, the Python code must be such that an image will be generated with that name.  By default, the image is saved in and retrieved from a special Crunchy &lt;span style="font-style: italic;"&gt;temp&lt;/span&gt; directory.&lt;br /&gt;&lt;br /&gt;While the approach used works, it does also mean that images can't be generated and displayed using a simple Python interpreter, nor can they be displayed from an arbitrary location.   At least that was the case until now.&lt;br /&gt;&lt;br /&gt;Prompted by a suggestion from Johannes, I wrote a very simple module whose core consists of only 7 Python statements, and which does away entirely with the cumbersome vlam image file option.  Images can now be loaded and displayed from anywhere using two lines of code:&lt;br /&gt;&lt;br /&gt;   &lt;span style="color: rgb(0, 153, 0); font-weight: bold;"&gt;import&lt;/span&gt; image_display&lt;br /&gt;   image_display.show(&lt;span style="font-style: italic;"&gt;path, [width=400, height=400]&lt;/span&gt;)&lt;br /&gt;&lt;br /&gt;And by &lt;span style="font-style: italic;"&gt;anywhere&lt;/span&gt;, I mean from any interactive Python element (interpreter, editor),&lt;br /&gt;and using any image source (local images and images on the web), like&lt;br /&gt;&lt;br /&gt;image_display.&lt;span class="output" id="out_4:7"&gt;&lt;span class="stdin"&gt;show('&lt;a href="http://imgs.xkcd.com/comics/python.png"&gt;http://imgs.xkcd.com/comics/python.png&lt;/a&gt;')&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;Furthermore, one can use this approach to create a "slide show" by alternating image_display.show() and time.sleep().&lt;br /&gt;&lt;br /&gt;Since there is no more need to use the old image_file option, it will be removed in the next Crunchy release.  I may have to give some more thoughts to the API for this new option (e.g. is there a better name than image_display?  Should I add a function for a slide show, giving a list of images and a time delay? etc.); suggestions are welcome.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-9176412109266498324?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/9176412109266498324/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=9176412109266498324' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/9176412109266498324'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/9176412109266498324'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/01/more-power-by-removing-option.html' title='More power and removing an option'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-7801725918698130895</id><published>2008-01-17T21:34:00.000-04:00</published><updated>2008-01-17T22:11:44.468-04:00</updated><title type='text'>100 posts, olpc, rst and Crunchy</title><content type='html'>This is the 100th post on this blog which I started to write a little over 3 years ago - shortly after I started my programming hobby.  And, as it so happens, I received two gifts in the past 24 hours:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;I received my give-one-get-one olpc in the mail today.&lt;/li&gt;&lt;li&gt;A student wrote a simple programs to add Crunchy-specific "directives" for docutils so that rst can be used to create Crunchy ready tutorials.  More on this below.&lt;/li&gt;&lt;/ul&gt;I had seen the olpc at Pycon 2007 but, not having tried it back then, I did not realise how small the keyboard really is.  I have fairly small hands but they are much too big to use the keyboard comfortably.  I thought of using it to write this blog entry but quickly gave up on that idea.&lt;br /&gt;&lt;br /&gt;It will take a while to fully explore the olpc.  I am extremely impressed by the quality of the screen.  However, it is a slow computer which apparently can only handle a "small" number of applications running concurrently - I managed to freeze it with about 8 applications running.  I seemed to remember from the Pycon presentation that the track pad had 2 (or 3, depending on how you count them) active region, but only the central one seems to be active.  Members of my family were surprise at the "plastic toy" appearance of the olpc but they all seem to be fairly impressed once they saw it running. &lt;br /&gt;&lt;br /&gt;Now, all I have to do is to figure out how to make Crunchy run on it. :-)&lt;br /&gt;&lt;br /&gt;Speaking of Crunchy, as part of Google's HOP contest, I had set up two tasks related to reStructured Text.  One of them was to write a plugin so that a rst file could be loaded, transformed into a html file (by docutils) and displayed by Crunchy.  This was done a while ago as &lt;a href="http://aroberge.blogspot.com/2007/12/restructuredtext-files-and-crunchy.html"&gt;I reported here&lt;/a&gt;.  However, when this is done, Crunchy treats all the code elements on a page (inside "pre" tags) the same way, which is specified via the variable crunchy.no_markup.  This does not allow for the same fine-tuning that is possible with the addition of vlam (very little added markup) to a normal html page.&lt;br /&gt;&lt;br /&gt;The second rst task was to write "directives" so that vlam could be inserted inside rst pages.  I had read quickly about rst directives and figured this would be fairly complicated.  From my superficial reading, I *thought* it would require a modification to docutils that would have to be incorporated into the core (or make a special version of docutils that incorporated those directives).   Of course this would make little sense.  In any event, a student wrote a 200 lines program that defined most required "rst directives" for Crunchy, allowing to take a rst file as input and output an html file.  All I had to do was to cut-and-paste the student's program into the existing rst plugin, change 2 lines of code, and I now have a fully working vlam-compatible rst loader for Crunchy.&lt;br /&gt;&lt;br /&gt;This will be part of the next Crunchy release.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-7801725918698130895?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/7801725918698130895/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=7801725918698130895' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/7801725918698130895'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/7801725918698130895'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/01/100-posts-olpc-rst-and-crunchy.html' title='100 posts, olpc, rst and Crunchy'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-8950875612943206184</id><published>2008-01-10T22:02:00.000-04:00</published><updated>2008-01-10T22:23:20.063-04:00</updated><title type='text'>Small tip for porting unit tests to Python 3.0</title><content type='html'>While working on increasing test coverage of Crunchy, I encountered a puzzling change when running doctest based unit tests under Python 3.0a and Python 2.5.   One test (of a function that was definitely too long) was failing under Python 3.0a1/2 with a single line containing the number 20 appearing in the output - in addition to all the other expected ones.  To make a long story short, the problem was linked to &lt;a href="http://www.python.org/dev/peps/pep-3116/"&gt;PEP 3116&lt;/a&gt;.  Using Python 3.0a1/2, when a file is written using ".write()", there is a return value corresponding to the number of bytes written; the "old" behavior was to return None.  In order to have doctest-based tests running successfully under both 2.x and 3.x, we need to replace things like&lt;br /&gt;&lt;br /&gt;f.write()&lt;br /&gt;&lt;br /&gt;by&lt;br /&gt;&lt;br /&gt;dummy = f.write()&lt;br /&gt;&lt;br /&gt;I understand that the plan is to backport many of the Python 3.0 features to Python 2.6 so as to ease the transition, and use an automated tool for the conversion.  I don't think that features from PEP 3116 will be implemented in Python 2.6, since they are very different from 2.5.  And I am doubtful that the conversion tool will take care of including a dummy assignment for functions that will now return a value different than None.  I am hoping that this post might end up saving a little bit of time for any reader that tries to migrate their code from 2.x to 3.0.&lt;br /&gt;&lt;br /&gt;On a different topic altogether, I have fixed the bug which resulted in removing the styles from remote web pages when using Python 3.0 in conjunction with Crunchy.  This will make Crunchy a more attractive tool to use in going through the official Python tutorial for Python 3.0.    For the moment, the Crunchy embedded interpreter still only works with Python 3.0a1 and not 3.0a2.  Hopefully I'll have this resolved in  new public release by Pycon 2008 so that Johannes can demonstrate it in his talk.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-8950875612943206184?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/8950875612943206184/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=8950875612943206184' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/8950875612943206184'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/8950875612943206184'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/01/small-tip-for-porting-unit-tests-to.html' title='Small tip for porting unit tests to Python 3.0'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-1447494242676533471</id><published>2008-01-07T20:52:00.000-04:00</published><updated>2008-01-07T21:36:27.054-04:00</updated><title type='text'>More encoding pains...</title><content type='html'>&lt;span style="font-weight: bold; font-style: italic;"&gt;&lt;span style="font-weight: bold;"&gt;&lt;span style="font-style: italic;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;It seems like every early January brings some new encoding pains...&lt;span style="font-weight: bold; font-style: italic;"&gt;&lt;span style="font-weight: bold;"&gt;&lt;span style="font-style: italic;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Eons&lt;/span&gt; ago (&lt;span style="font-size:78%;"&gt;less than a year&lt;span style="font-size:100%;"&gt;)&lt;/span&gt;&lt;/span&gt; I used to program on a Windows based computer.  My Windows user name was simply André, which is not surprising since it's my first name.  However, the observant reader may have noticed that my name requires one non-ASCII character.  Such a small detail...  that can cause so much annoyance.&lt;br /&gt;&lt;br /&gt;Two years ago, on January 6th, I &lt;a href="http://aroberge.blogspot.com/2006/01/note-to-self-about-site-customization.html"&gt;wrote about using site customization&lt;/a&gt; so that my favourite Python editor at the time (SPE) could deal with my user name. &lt;br /&gt;&lt;br /&gt;Last year, on January 3rd, &lt;a href="http://aroberge.blogspot.com/2007/01/unicode-headaches-and-solution.html"&gt;I wrote about how Crunchy dealt with encoding issues&lt;/a&gt; in a way that was independent of any site customizations.  At the time, &lt;a href="http://www.blogger.com/profile/13103273429450824594"&gt;an astute reader&lt;/a&gt; made the following comment (which I had forgotten until today, when I decided to write this blog entry):&lt;br /&gt;&lt;br /&gt;&lt;blockquote style="font-style: italic;"&gt;Without having looked at the rest of your code, so I might be completely off here, this somehow looks wrong:&lt;br /&gt;&lt;br /&gt;result = result.decode(sys.getdefaultencoding()).encode('utf-8')&lt;br /&gt;&lt;br /&gt;The reason I say this is that you're decoding and encoding in the same place. Since Python unicode support is so good, it's generally a good idea to decode to unicode any use input you get as early as possible, and to encode only as late as possible when outputting strings. Since you're doing complicated web ui stuff here, so it may be that you're not doing anything with 'result' between input and returning it to the browser, but if you are, the string should have already been decoded by the time it gets to this line. &lt;span style="font-weight: bold;"&gt;Otherwise this will bite you anytime you try to do anything with the string like simple concatenation. &lt;/span&gt;&lt;span style="font-size:78%;"&gt;[emphasis added]&lt;/span&gt;&lt;br /&gt;&lt;/blockquote&gt;Of course, since Crunchy was working properly, I quickly dismissed that comment.  With the way everything was implemented, Crunchy was working just fine...  In fact, to this day, if you download the latest public release (0.9.8.6), everything works just fine - even if your user name includes non-ASCII characters.&lt;br /&gt;&lt;br /&gt;However ... in adapting Crunchy to works with Python 3.0, &lt;span style="font-weight: bold;"&gt;I do things with the various strings like simple concatenation &lt;/span&gt;... and, of course, this cause some problems as I found out when I "borrowed" the old Windows computer from my daughter to try the latest changes I had made. &lt;br /&gt;&lt;br /&gt;It. Did. Not. Work.&lt;br /&gt;&lt;br /&gt;Ok, so I have two possible solutions:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Trade with my daughter for a while, letting her use my MacBook (which she loves!) while I use the "old" Windows desktop.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Create an account with an accented name on my MacBook.&lt;/li&gt;&lt;/ol&gt;Well, as much as I love my daughter, I could not face the pain of going back to using the Windows desktop as my main computer.  So, solution 2 was an easy choice.&lt;br /&gt;&lt;br /&gt;Except that it wasn't....&lt;br /&gt;&lt;br /&gt;My account name on the mac is my full name (André Roberge).  Hmmm... this has already a non-ASCII character.    But my home directory is "andre" - no accent.  Under Mac OS, a user has a full name that appears in the login window, and a short name used for the root directory (/Users/andre in my case).  When I tried to create a new account with a non-ASCII character in the short name, it just beeped and refused to enter it.&lt;br /&gt;&lt;br /&gt;In search for an answer, I posted on three Mac related forums, got either no answer or some unhelpful and wrong answer on two of them ... I was considering posting on the Python list, but, fortunately, I eventually got a useful answer.&lt;br /&gt;&lt;br /&gt;So, if you are thinking of writing i18n applications that either can run from any directory or that save information in the user's default directory, or both, and you want to make sure that it will work on a Windows computer, here's how you can do it on a Mac (under OS X 10.5):&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Create a test account user the account manager under any name of your choosing; however, the "short name" will have to be ASCII only (at this stage).&lt;/li&gt;&lt;li&gt;From the account manager, ctrl-click on the account after it has been created; this will bring an advanced dialog.&lt;/li&gt;&lt;li&gt;Edit the advanced dialog to change the short name, and the home directory, to the desired value.  I chose the name "accentué"  (self-referencing name, if you know French).  Note that doing so does not change the actual name of the directory.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Go to a terminal window and do "sudo mv old_name new_name" to change the name of the home directory that was created at step 1.&lt;/li&gt;&lt;/ol&gt;After I did all this, the development version of Crunchy did not work from the new account.  This pleased me very much: it likely will meant that I do not have to trade computers with my daughter to continue working on Crunchy. ;-)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-1447494242676533471?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/1447494242676533471/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=1447494242676533471' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/1447494242676533471'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/1447494242676533471'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2008/01/more-encoding-pains.html' title='More encoding pains...'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-4134869329651483982</id><published>2007-12-30T22:21:00.001-04:00</published><updated>2007-12-30T23:11:28.633-04:00</updated><title type='text'>More about Crunchy running under Python3.0a1</title><content type='html'>Just in time for the new year, and after a major reorganization of Crunchy's code to make it easier to write more unit tests for it, I did manage to get all of Crunchy's core functionality working under Python 3.0a1.  Crunchy can now import html files that are not conforming to strict xml convention (i.e. not having closing tags for some elements) and thus create problems for ElementTree.  The way it does that is to launch an external process under Python 2.5 (or whatever default version that is invoked via typing "python" at a terminal - but it has to be a 2.x version for it to work).  This process imports the file, "cleans" it (using a combination of BeautifulSoup, ElementTree and Crunchy's security module), and saves it locally.   After a delay, Crunchy loads up the cleaned up file and display it.  In this process, most of the styling is unfortunately removed.  :-(&lt;br /&gt;&lt;br /&gt;However, the good news is that I was able to load up the &lt;a href="http://docs.python.org/dev/3.0/tutorial/index.html"&gt;official 3.0 Python tutorial&lt;/a&gt; (work in progress) and try it out using Crunchy.  I did find out one limitation of using Crunchy to do so.  Crunchy encodes the Python output using utf-8 before forwarding to the browser.  So, instead of having things like &lt;span style="color: rgb(0, 102, 0); font-weight: bold;"&gt;b'Andr\xc3\xa9'&lt;/span&gt; appearing on the screen, it would be converted to &lt;span style="font-weight: bold; color: rgb(0, 102, 0);"&gt;André&lt;/span&gt;.  Thus, Crunchy is not a very good platform to teach about encoding/decoding of strings.  For other aspects though, it is an ideal tool (if I may say so) for going through the Python tutorial: there is no need to switch back and forth between the browser and a separate Python environment to try things out.  I still hope to have the time and energy to go through the entire 3.0 tutorial (something I have never done before, for 2.x) and see if I can find any bugs or come up with useful suggestions.&lt;br /&gt;&lt;br /&gt;All I have left to do for the next release is to write up some documentation/tutorial on the new Turtle module and on launching Crunchy under Python3.0a1.   With a bit of luck, this will all be finished before the end of the year.&lt;br /&gt;&lt;br /&gt;In addition to the code reorganization mentioned above, I did fix a few bugs and made an improvement on Crunchy's Borg interpreters.  For those that aren't familiar with it, Crunchy allows to embed a number of interpreters (html input box communicating with the Python backend) within a single page.  These interpreters can either be isolated one from another (meaning that a variable defined in one interpreter is only known by that interpreter) or can share a common environment (aka Borg interpreters).   Normally, in a single user mode, using a single open tab in Firefox, every time a new page is displayed, the Borg interpreters are effectively reset (the old ones are garbage collected and the new ones are created from an empty slate).&lt;br /&gt;&lt;br /&gt;Previously, if one were to have multiple users (or multiple tabs open from the same user) on the same Crunchy server, all Borg interpreters ended up sharing the exact same state.  This is not very convenient if two users are trying to use the same variable names!    I had planned to address this "feature" at some point after the 1.0 release but was forced into it earlier due to the 3.0a1 work.  The reason is the following.&lt;br /&gt;&lt;br /&gt;To create "Borg interpreters", I was using the Borg idiom invented by Alex Martelli.  It goes as follows:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 102, 0); font-weight: bold;"&gt;class Borg(object):&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 102, 0); font-weight: bold;"&gt;    '''Borg Idiom, from the Python Cookbook, 2nd Edition, p:273&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 102, 0); font-weight: bold;"&gt;    Derive a class form this; all instances of that class will share the&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 102, 0); font-weight: bold;"&gt;    same state, provided that they don't override __new__; otherwise,&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 102, 0); font-weight: bold;"&gt;    remember to use Borg.__new__ within the overriden class.&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 102, 0); font-weight: bold;"&gt;    '''&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 102, 0); font-weight: bold;"&gt;    _shared_state = {}&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 102, 0); font-weight: bold;"&gt;    def __new__(cls, *a, **k):&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 102, 0); font-weight: bold;"&gt;        obj = object.__new__(cls, *a, **k)&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 102, 0); font-weight: bold;"&gt;        obj.__dict__ = cls._shared_state&lt;/span&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 102, 0); font-weight: bold;"&gt;        return obj&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;When using this idiom in a standard program under 3.0a1/2, a deprecation warning is raised about &lt;span style="font-weight: bold; color: rgb(0, 102, 0);"&gt;object.__new__()&lt;/span&gt; not taking any argument.  When I was trying to make use of this idiom in Crunchy running under 3.0a1/2, the deprecation warning was actually replaced by an exception.  Rather than trying to silence this exception, I decided to take a different approach and used instead the following:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;span style="font-weight: bold; color: rgb(0, 102, 0);"&gt;class BorgGroups(object):&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold; color: rgb(0, 102, 0);"&gt;    '''Inspired by the Borg Idiom, from the Python Cookbook, 2nd Edition, p:273&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold; color: rgb(0, 102, 0);"&gt;    to deal with multiple Borg groups (one per crunchy page)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold; color: rgb(0, 102, 0);"&gt;    while being compatible with Python 3.0a1/2.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold; color: rgb(0, 102, 0);"&gt;    Derived class must use a super() call to work with this properly.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold; color: rgb(0, 102, 0);"&gt;    '''&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold; color: rgb(0, 102, 0);"&gt;    _shared_states = {}&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold; color: rgb(0, 102, 0);"&gt;    def __init__(self, group="Borg"):&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold; color: rgb(0, 102, 0);"&gt;        if group not in self._shared_states:&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold; color: rgb(0, 102, 0);"&gt;            self._shared_states[group] = {}&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold; color: rgb(0, 102, 0);"&gt;        self.__dict__ = self._shared_states[group]&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic; color: rgb(51, 255, 51);"&gt;# The following BorgConsole class is defined such that all instances&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic; color: rgb(51, 255, 51);"&gt;# of an interpreter on a same html page share the same environment.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold; color: rgb(0, 102, 0);"&gt;class BorgConsole(BorgGroups, SingleConsole):&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold; color: rgb(0, 102, 0);"&gt;    '''Every BorgConsole share a common state'''&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold; color: rgb(0, 102, 0);"&gt;    def __init__(self, locals={}, filename="Crunchy console", group="Borg"):&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold; color: rgb(0, 102, 0);"&gt;        super(BorgConsole, self).__init__(group=group)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold; color: rgb(0, 102, 0);"&gt;        SingleConsole.__init__(self, locals, filename=filename)&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;The "group" variable is taken to be a unique id generated by Crunchy each time it processes a given html page.  Thus, each page loaded by a different user (or the same user, at a different time) from the same Crunchy server will result in a unique set of Borg interpreters.&lt;br /&gt;&lt;br /&gt;To be fair, I must admit that I did not come up with this solution totally on my own.  A while ago, I asked for something like this on comp.lang.python. (Those interested in the details should search for "Borg rebellion".)  I just derived the above solution from some suggestions made at that time.&lt;br /&gt;&lt;br /&gt;Finally, in addition to all this, I found out a bug in code.py for Python 3.0a1/2.  I tried to send an email to the python-3000 mailing list about it, but it was held up, waiting for a moderator approval for a few days.  So, I canceled it and filed a &lt;a href="http://bugs.python.org/issue1707"&gt;bug report&lt;/a&gt; instead (which I should have done in the first place) on the bug tracker.  I still haven't seen any follow up - perhaps due to the title I gave it.  The bug is actually very easy to fix - three lines of code need to be replaced by a single one.  The solution is related to my only other &lt;a href="http://www.python.org/dev/peps/pep-3111/"&gt;"official" contribution&lt;/a&gt; to Python to date.   Hopefully, by this time next year, I'll have learned enough to contribute more to Python.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-4134869329651483982?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/4134869329651483982/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=4134869329651483982' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/4134869329651483982'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/4134869329651483982'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2007/12/more-about-crunchy-running-under.html' title='More about Crunchy running under Python3.0a1'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-4287350583461503755</id><published>2007-12-27T22:30:00.001-04:00</published><updated>2007-12-27T23:14:18.654-04:00</updated><title type='text'>Crunchy and Python 3.0a2</title><content type='html'>Continuing with my experiment of adapting Crunchy to Python 3.0, I managed to get Crunchy to start with Python 3.0a2 and get some code running from the editor - but not from the interpreter, nor the doctest.  Most of the problems I have are dealing with bytes-to-string conversion and string-to-bytes.  As mentioned by Guido van Rossum &lt;a href="http://www.artima.com/weblogs/viewpost.jsp?thread=208549"&gt;last June&lt;/a&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;/blockquote&gt;&lt;ul style="font-style: italic;"&gt;&lt;li&gt;&lt;span style="font-size:85%;"&gt;We're switching to a model known from Java: (immutable) text strings are Unicode, and binary data is represented by a separate mutable "bytes" data type.  In addition, the parser will be more Unicode-friendly: the default source encoding will be UTF-8, and non-ASCII letters can be used in identifiers&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;Later on, in a comment from that post, we find:&lt;br /&gt;&lt;ul style="font-style: italic; font-family: times new roman;"&gt;&lt;li&gt;&lt;span style="color: rgb(0, 0, 0);"&gt;        &gt; In your presentation last night you had one slide which&lt;br /&gt;&gt; talked about the "str" vs "bytes" types in Python 3000. On&lt;br /&gt;&gt; the bottom of that slide was something like:&lt;br /&gt;&gt;&lt;br /&gt;&gt; str(b"asdf") == "b'asdf'"&lt;br /&gt;&gt;&lt;br /&gt;&gt; However, in discussing this slide (very briefly) you said&lt;br /&gt;&gt; that a type constructors like "str" could be used to do&lt;br /&gt;&gt; conversion. It seems like "str" is behaving more like&lt;br /&gt;&gt; "repr" in this case, which seems unusual and less useful&lt;br /&gt;&gt; to me. Was this a typo, or is this actually the way it's&lt;br /&gt;&gt; supposed to work? What's the rationale?&lt;br /&gt;&lt;br /&gt;To be honest, this is an open issue. The slide was wrong compared to the current implementation; but the implementation currently defaults to utf8 (so str(b'a') == 'a'), which is not right either. The problem is that there are conflicting requirements: str() of any object should ideally always return something, but we don't want str() to assume a specific default encoding.&lt;br /&gt;&lt;br /&gt;To be continued...&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;This change seems innocuous enough...&lt;br /&gt;&lt;br /&gt;As a web server, Crunchy sends to and receives information from the browser as "binary data" or "bytes".  As a generalized Python interpreter, Crunchy manipulates the information as "strings".  It appears that the "bytes" implementation is done much more completely in Python 3.0a2 than it was in Python 3.0a1.  And this is the source of many problems.&lt;br /&gt;&lt;br /&gt;For example, Crunchy sends from the browser some information about the path to which a Python file should be saved and its content as follows:&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 0, 153); font-weight: bold;"&gt;'/Users/andre/.crunchy/temp.py&lt;span style="color: rgb(0, 153, 0);"&gt;_::EOF::_&lt;/span&gt;from Tkinter import *\nroot = Tk()\nw = Label(root, text="Crunchy!")\nw.pack()\nroot.mainloop()'&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;This is sent as a binary stream which needs to be converted to the string written above.  This conversion is done via str(...).  Using Python 3.0a1 (and 2.4 and 2.5), the result was as above; splitting the string gave the following:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold; color: rgb(0, 0, 153);"&gt;&lt;span style="color: rgb(51, 204, 0);"&gt;[&lt;/span&gt;'/Users/andre/.crunchy/temp.py'&lt;span style="color: rgb(51, 204, 0);"&gt;, &lt;/span&gt;'from Tkinter import *\nroot = Tk()\nw = Label(root, text="Crunchy!")\nw.pack()\nroot.mainloop()'&lt;span style="color: rgb(51, 204, 0);"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Now, with Python 3.0a2, it gets slightly more complicated.  The first string acquires a "b" prefix upon conversion (as mentioned in the comment from Guido's blog mentioned before). After splitting, the result is&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold; color: rgb(51, 204, 0);"&gt;[&lt;/span&gt;&lt;span style="color: rgb(0, 0, 153); font-weight: bold;"&gt;"b'/Users/andre/.crunchy/temp.py"&lt;/span&gt;&lt;span style="font-weight: bold;"&gt;&lt;span style="color: rgb(51, 204, 0);"&gt;,&lt;/span&gt; &lt;/span&gt;&lt;span style="color: rgb(0, 0, 153); font-weight: bold;"&gt;'from Tkinter import *\\nroot = Tk()\\nw = Label(root, text="Crunchy!")\\nw.pack()\\nroot.mainloop()\''&lt;/span&gt;&lt;span style="font-weight: bold;"&gt;&lt;span style="color: rgb(51, 204, 0);"&gt;]&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt; So, we now have a first string with a "&lt;span style="font-weight: bold; color: rgb(0, 0, 153);"&gt;b'&lt;/span&gt;" prefix embedded in it, and a second one without. It seems that each case will have to be handled carefully on its own.  And I suspect more problems will show up as we get closer to the final 3.0 release.&lt;br /&gt;&lt;br /&gt;I know, I know, I'm really not following the "recommended" practice, as quoted on Guido's blog.  I should probably wait first for Python 2.6 to come out.  Then, I should have a complete unit test coverage and use the conversion tool to create a Python 3.0 version ....   However, I am not convinced that the conversion tool will be smart enough to know when a function (that I write) expect a "str" object and when it expect a "byte" one.  Furthermore, the few unit tests I had worked fine under both Python 2.5 and 3.0 ... but some functions that I had written with the expectation that they would receive some string arguments did not work in "production code", as they were getting some bytes arguments.  And this failed completely silently...&lt;br /&gt;&lt;br /&gt;If I had to give some advice to someone about creating Python programs that can work with both Python 2.x and Python 3.x, I would say like Guido: &lt;span style="font-weight: bold;"&gt;don't&lt;/span&gt;.  :-) Unless of course you are like me and are doing this for fun and to get to learn about the differences between Python 2.x and 3.x along the way.   But then, "be prepared for the unexpected" like the following: turning on a few print statements (via a "debug flag") can result in breaking some code; turn them off and the code works again... Yes, it did happen to me - I still have to figure out how...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-4287350583461503755?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/4287350583461503755/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=4287350583461503755' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/4287350583461503755'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/4287350583461503755'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2007/12/crunchy-and-python-30a2.html' title='Crunchy and Python 3.0a2'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-6160583254716289558</id><published>2007-12-27T16:27:00.001-04:00</published><updated>2007-12-27T16:50:07.399-04:00</updated><title type='text'>Crunchy and Python 3.0a1</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp3.blogger.com/_KpSgL5zKAvE/R3QLJdeYK4I/AAAAAAAAAAk/mDfK1uotPZs/s1600-h/crunchy-Py3.0a1.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp3.blogger.com/_KpSgL5zKAvE/R3QLJdeYK4I/AAAAAAAAAAk/mDfK1uotPZs/s400/crunchy-Py3.0a1.png" alt="crunchy running under Python 3.0a1" id="BLOGGER_PHOTO_ID_5148752531150089090" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;It is often said that a picture is worth a thousand words...&lt;br /&gt;&lt;br /&gt;I have managed to make Crunchy run under Python 3.0a1.  Some of the features are not working but the interpreter and the editor work.  The turtle module I have been working on also works "nicely" (read: as slow as before) with this new Python version.  Unfortunately, when it is run under Python 3.0a1, Crunchy can not load most pages - including those of the official Python 3.0 tutorial.  The reason is that is uses ElementTree to parse pages and it is unforgiving when it comes to having unclosed tags (as in &amp;lt;link&amp;gt; and &amp;lt;meta...&amp;gt; for example); it also seems to not be able to handle the &amp;lt;script&amp;gt;s that are included on the page.  I have not yet found a way to reliably "clean" the pages before parsing them with ElementTree.  While I believe that I should be able to do so with a bit more work, there is a bigger problem...&lt;br /&gt;&lt;br /&gt;Unfortunately, Crunchy does not run under Python 3.oa2, and the error messages I get have not been too helpful in figuring out the error.  However, perhaps this is due to a faulty installation.  What makes me think so is that when I start a 3.0a2 session at a terminal, I get an error message when I use exit().  This is most unexpected.&lt;br /&gt;&lt;br /&gt;In any even, the next release should include the new crunchy turtle module and be usable with 3.0a1.   Perhaps Johannes, or some curious user, will be able to figure out how to make it run under 3.0a2 as well.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-6160583254716289558?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/6160583254716289558/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=6160583254716289558' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/6160583254716289558'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/6160583254716289558'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2007/12/crunchy-and-python-30a1.html' title='Crunchy and Python 3.0a1'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp3.blogger.com/_KpSgL5zKAvE/R3QLJdeYK4I/AAAAAAAAAAk/mDfK1uotPZs/s72-c/crunchy-Py3.0a1.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-5009822449100911277</id><published>2007-12-25T16:18:00.001-04:00</published><updated>2007-12-25T16:18:02.784-04:00</updated><title type='text'>Slow turtle ... in time for Xmas
</title><content type='html'>One of the task assigned in Google's HOP contest was to design a simple turtle graphics module for Crunchy.  This was done successfully by a student as a prototype.  This prototype had some unfortunate limitations in terms of number of turtles and simultaneous graphics canvas existing on the same page, but it did give me the impetus to use the student code as a proof-of-concept and implement a more complete turtle module for Crunchy.&lt;br /&gt;&lt;br /&gt;Playing with turtles, and trying to draw fairly complex shapes, made me realize that the combination of using an html canvas and the Crunchy comet communication makes for an extremely &lt;span style="font-weight: bold;"&gt;slow&lt;/span&gt; turtle. It would be really nice to  find a better (faster) way.&lt;br /&gt;&lt;br /&gt;The next Crunchy release should include that turtle module ... and an additional bonus: Crunchy can now be launched successfully using either Python 2.5 (or 2.4) and Python 3.0a1.  And the turtle module works with both.&lt;br /&gt;&lt;br /&gt;At the moment, not all of Crunchy's features are supported when using Python 3.0.  However, this should no longer be the case by the time version 1.0 comes out.&lt;br /&gt;&lt;br /&gt;And, for those that might be tempted to point out Guido's blog entry about not making programs compatible with both Python 2.x and 3.x, please don't bother.  I realize that it is not wise in general to try to do so.  However, given Crunchy's design philosophy to make it as easy for students/teachers/tutorial writers to use - it just does make sense: download, unzip, double-click; nothing else should be needed to start having fun with Python - no matter what new Python version gets installed.&lt;br class="khtml-block-placeholder"&gt;&lt;br /&gt;        &lt;br&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-5009822449100911277?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/5009822449100911277/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=5009822449100911277' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/5009822449100911277'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/5009822449100911277'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2007/12/slow-turtle-in-time-for-xmas.html' title='Slow turtle ... in time for Xmas&#xA;'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-6655524594848300031</id><published>2007-12-18T23:22:00.000-04:00</published><updated>2007-12-19T07:04:53.990-04:00</updated><title type='text'>(NOT) Bitten by PEP 3113</title><content type='html'>&lt;span style="font-weight: bold; color: rgb(0, 0, 153);"&gt;UPDATE:  The comments left on this post (1 and 3) in particular corrected my misreading of PEP 3113.  There is no such wart as I describe in Python 3.0.  I should have known better than to question GvR and friends. :-)  &lt;/span&gt;&lt;span style="color: rgb(0, 0, 153);"&gt;I'm leaving this post as a reference.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;In trying to make Crunchy useful &amp;amp; interesting for beginning programmers to learn Python, I designed a small graphics library following some "natural" notation.  As an aside, Johannes Woolard is the one who made sure that this library could be easily used interactively within Crunchy.  I mention his name since too many people seem to assume that I am the only one involved in Crunchy's design.  Anyway, back to the library...&lt;br /&gt;&lt;br /&gt;In that library, the function used to draw a line between two points uses the syntax&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0); font-weight: bold;"&gt;line(&lt;/span&gt;(x1, y1),  (x2, y2)&lt;span style="color: rgb(51, 204, 0); font-weight: bold;"&gt;&lt;span style="color: rgb(0, 153, 0);"&gt;)&lt;/span&gt;   &lt;/span&gt;&lt;span style="color: rgb(51, 204, 0);"&gt;&lt;span style="color: rgb(0, 0, 0);"&gt;&lt;br /&gt;&lt;br /&gt;      for example:   &lt;span style="color: rgb(0, 153, 0); font-weight: bold;"&gt;line(&lt;/span&gt;(100, 100),  (200, 200)&lt;span style="color: rgb(0, 153, 0); font-weight: bold;"&gt;) &lt;span style="color: rgb(51, 51, 255);"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;which should be familiar to everyone.  Unfortunately, following the implementation of &lt;a href="http://www.python.org/dev/peps/pep-3113/"&gt;PEP 3113&lt;/a&gt; in Python 3.0, this syntax is no longer allowed.  This is ... annoying!  There are two alternatives I can use:&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0); font-weight: bold;"&gt;line(&lt;/span&gt;x1, y1, x2, y2&lt;span style="color: rgb(0, 153, 0); font-weight: bold;"&gt;)&lt;br /&gt;&lt;/span&gt;&lt;span style="color: rgb(51, 204, 0);"&gt;&lt;span style="color: rgb(0, 0, 0);"&gt;&lt;br /&gt;   for example: &lt;span style="font-weight: bold; color: rgb(0, 153, 0);"&gt;line(&lt;/span&gt;100, 100, 200, 200&lt;span style="color: rgb(0, 153, 0); font-weight: bold;"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;or&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 153, 0); font-weight: bold;"&gt;line(&lt;/span&gt;point_1,  point_2&lt;span style="color: rgb(0, 153, 0); font-weight: bold;"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;where point_a = (x_a, y_a).  &lt;span style="color: rgb(51, 51, 255);"&gt;&lt;span style="font-weight: bold;"&gt;Update: with this second definition, it will be possible to invoke the function as &lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: rgb(51, 204, 0);"&gt;&lt;span style="color: rgb(0, 0, 0);"&gt;&lt;span style="color: rgb(0, 153, 0); font-weight: bold;"&gt;line(&lt;/span&gt;(100, 100),  (200, 200)&lt;span style="color: rgb(0, 153, 0); font-weight: bold;"&gt;) &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Of course, either of these two option is easy to implement (and is going to be backward compatible with Python 2k).  However, I don't find either one of them particularly clear for beginners (who might be familiar with the normal mathematical notation) and &lt;span style="color: rgb(51, 51, 255);"&gt;&lt;span style="font-weight: bold;"&gt;do not&lt;/span&gt; &lt;/span&gt;consider this a (small) wart of Python 3k.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-6655524594848300031?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/6655524594848300031/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=6655524594848300031' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/6655524594848300031'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/6655524594848300031'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2007/12/bitten-by-pep-3113.html' title='(NOT) Bitten by PEP 3113'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-4668673239468363338</id><published>2007-12-18T20:00:00.001-04:00</published><updated>2007-12-18T20:11:45.354-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='crunchy'/><category scheme='http://www.blogger.com/atom/ns#' term='ghop'/><title type='text'>reStructuredText files and Crunchy</title><content type='html'>Crunchy can now handle reStructuredText (.rst) files in the same way it can process plain html ones!  This requires the user to have docutils installed - which is normally the case for anyone that writes .rst files.&lt;br /&gt;&lt;br /&gt;The test coverage for Crunchy is slowly improving.  Currently, 10 modules are mostly covered by doctest-based unit tests, out of approximately 40.  Since I make use of .rst files to keep the unit tests, these can now be browsed "pleasantly" using Crunchy itself.&lt;br /&gt;&lt;br /&gt;Furthermore ... all the unit tests written so far work under Python 2.4, Python 2.5, and ... Python 3.0a1!   This required some tedious rewriting of some parts of the code but the end result is well worth it - if only to really learn about differences between Python 2.5 and Python 3.0.&lt;br /&gt;&lt;br /&gt;One thing that I found, which will be no surprise to TDD aficionados, is that code written without testing in mind can be quite tricky to write comprehensive tests for.  Add to this the extra complication of making that code run under two incompatible Python versions, and you are on your way to major headaches.  It's a good thing I am doing this only for fun!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-4668673239468363338?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/4668673239468363338/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=4668673239468363338' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/4668673239468363338'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/4668673239468363338'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2007/12/restructuredtext-files-and-crunchy.html' title='reStructuredText files and Crunchy'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-4055726983078611596</id><published>2007-12-08T00:04:00.000-04:00</published><updated>2007-12-08T00:19:21.269-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='crunchy'/><category scheme='http://www.blogger.com/atom/ns#' term='ghop'/><title type='text'>Launching Python 3.0 program from Crunchy running under Python 2.5</title><content type='html'>As part of Google's Highly Open Participation contest, Michele Mazzoni completed the task of creating a new option for Crunchy: one can now launch &lt;span style="font-size:78%;"&gt;(starting with the next release of Crunchy - 0.9.8.5&lt;/span&gt;) a program using a different version of Python than the one used by Crunchy itself.  While I had suggested that the alternate Python version could be set via the configuration options for Crunchy (usually accessible from a Python interpreter), Michele had the brilliant idea to add a simple input box where one can specify the path (or 'alias') of the Python version used right on the page where the program is launched from.  This makes it extremely easy to change the interpreter version used to launch a user written program.&lt;br /&gt;&lt;br /&gt;Michele has prepared a screencast demonstrating this, which should appear on &lt;a href="http://showmedo.com/"&gt;ShowMeDo&lt;/a&gt; hopefully soon.&lt;br /&gt;&lt;br /&gt;Thank you Michele - and thank you Google!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-4055726983078611596?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/4055726983078611596/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=4055726983078611596' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/4055726983078611596'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/4055726983078611596'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2007/12/launching-python-30-program-from.html' title='Launching Python 3.0 program from Crunchy running under Python 2.5'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-1022637696665248216</id><published>2007-12-04T07:50:00.000-04:00</published><updated>2007-12-04T19:02:02.437-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='crunchy'/><title type='text'>More results from GHOP</title><content type='html'>Google's Highly Open Participation (GHOP) contest is attracting a lot of attention from the right people: pre-university students.  The PSF is one of ten organizations mentoring students working on Python-related projects.  Since I submitted tasks suggestions early on and volunteered to help following a call for volunteers from Titus Brown, Crunchy has benefited from  many students contributions.  Crunchy's messages have been translated in Estonian, Macedonian, Polish and Italian with, hopefully, more translations to come.  Some new unit tests have been added with more to come.  There may be a couple of nice surprises coming out soon too :-)&lt;br /&gt;&lt;br /&gt;While other projects have also benefited from GHOP's students contributions, there could be more.  If you have some good ideas for mini-projects (doable in 3-5 days, at a couple of hours per day with perhaps one full day), your suggestions would most likely be most welcome.  Just check out the &lt;a href="http://groups.google.com/group/ghop-python"&gt;GHOP Python discussion group&lt;/a&gt;.  And, if you would like to join the (too small) ranks of Python mentors, please do; we need all the help we can get.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-1022637696665248216?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/1022637696665248216/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=1022637696665248216' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/1022637696665248216'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/1022637696665248216'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2007/12/more-results-from-ghop.html' title='More results from GHOP'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-3474551656659995981</id><published>2007-11-30T22:09:00.000-04:00</published><updated>2007-11-30T22:13:29.061-04:00</updated><title type='text'>First Crunchy screencast</title><content type='html'>I finally got around to produce a &lt;a href="http://showmedo.com/videos/video?name=1430000&amp;amp;fromSeriesID=143"&gt;first Crunchy screencast&lt;/a&gt;.  Since Crunchy is an interactive program, I thought I should do screencasts that reproduced the interactive feel: that is to say, I would record a live, more or less improvised session.  This means that the screen cast is not as polished as it could be - an even has a few minor mistakes in it.  Still, I think it gives a good (superficial) overview of what Crunchy is capable of.&lt;br /&gt;&lt;br /&gt;What do you think?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-3474551656659995981?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/3474551656659995981/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=3474551656659995981' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/3474551656659995981'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/3474551656659995981'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2007/11/first-crunchy-screencast.html' title='First Crunchy screencast'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-2723170381469328202</id><published>2007-11-28T21:16:00.000-04:00</published><updated>2007-11-28T22:33:49.240-04:00</updated><title type='text'>First result from GHOP: Crunchy Estonian</title><content type='html'>By now everyone has probably heard of &lt;a href="http://google-code-updates.blogspot.com/2007/11/its-here-google-highly-open.html"&gt;Google Highly Open Participation&lt;/a&gt; contest, and the &lt;a href="http://code.google.com/p/google-highly-open-participation-psf/"&gt;participation of the PSF&lt;/a&gt;.  And, initially through serendipity but later with more active involvement, a few Crunchy-related tasks were created including some involving translations.  And, just a day after the contest started, a first translation (in Estonian!) has been made for Crunchy by a student named Tanel.&lt;br /&gt;&lt;br /&gt;The tasks assigned are usually not very big but they provide nice stepping stones for getting people involved in open source software.  Thank you Google for this wonderful idea.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-2723170381469328202?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/2723170381469328202/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=2723170381469328202' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/2723170381469328202'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/2723170381469328202'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2007/11/first-result-from-ghop-crunchy-estonian.html' title='First result from GHOP: Crunchy Estonian'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-4569549096121094527</id><published>2007-11-27T20:04:00.000-04:00</published><updated>2007-11-27T20:28:36.535-04:00</updated><title type='text'>A failed experiment</title><content type='html'>On a whim, I decided to try a fun experiment: have Crunchy run correctly when invoked either from Python 2.4 or Python 2.5 ... or from Python 3.0! I thought that if I could make it to work, it would be a great tool to go through the 3.0 docs and tutorial, and finding out if there were any mistakes.  (I was planning to do this, hoping to help the developing team in my own limited way.)&lt;br /&gt;&lt;br /&gt;In the end, I had to give up, as Crunchy uses third-party modules (ElementTree, HTMLTreeBuilder, ElementSoup, BeautifulSoup, etc.) that I could not make compatible with both Python 2.x and 3.0 without essentially rewriting them in two separate modules each.  Actually, this was already done for ElementTree (included in the stdlib under a different name) but not for the others.  &lt;br /&gt;&lt;br /&gt;The main stumbling block was string handling/encoding...  This should not be a surprise to anyone who has followed the Py3k development.&lt;br /&gt;&lt;br /&gt;Still, I've learned a fair bit from that experiment.  One thing that I sort of knew already related to using print statements for debugging purpose.  I often sprinkle my code with "if DEBUG: print ...", having multiple print statements appearing.  Often I find that I want to have a few debug flags in the same file and think to myself &lt;span style="font-style: italic;"&gt;&lt;br /&gt;&lt;br /&gt;Shouldn't I replace these print statements by a debug() function with a variable setting the debug level ...&lt;br /&gt;&lt;span style="font-style: italic;"&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;If I would have done something like this, I could have had just a few print statements located in a debug.py module.   As it was, with the change in Py3k for print becoming a function, I had to do a lot of changes by hand.  Yes, it might have been possible to use the automated 2to3 tool ... but I wanted to maintain compatibility with Python 2.x  and, more importantly, get a feel for what was involved in making the code Py3k compatible.&lt;br /&gt;&lt;br /&gt;If or when BeautifulSoup, ElementSoup and HTMLBuilder become Py3k compatible, I'll have to give it a try again...&lt;span style="font-style: italic;"&gt;&lt;span style="font-style: italic;"&gt;&lt;span style="font-style: italic;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-4569549096121094527?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/4569549096121094527/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=4569549096121094527' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/4569549096121094527'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/4569549096121094527'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2007/11/failed-experiment.html' title='A failed experiment'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-1140155112427850645</id><published>2007-10-16T14:38:00.000-03:00</published><updated>2007-10-16T14:51:03.250-03:00</updated><title type='text'>Choosing a CMS ... Will Python make it</title><content type='html'>This post is not related to programming per se; feel free to skip it ...&lt;br /&gt;&lt;br /&gt;"We" are in the process of revamping our &lt;a href="http://www.usainteanne.ca"&gt;web site&lt;/a&gt;, and use a CMS.   We have just signed off on the first part of the project (new look and wireframe) and are looking at the second (and "final") stage. As we are too small to afford a commercial solution, we are looking at open source CMS.  So far, the preferred choice of our director of technology is Alfresco.  The choice recommended by our usual technology provider is Joomla!  My favourite would likely be Plone - given it is written in Python ;-)&lt;br /&gt;&lt;br /&gt;Normally, we would proceed with a public tender - but given that there are few local providers, we may just proceed with the same firm that provided services in phase one.   However ...  If some of you reading this have work experience in this area, and would be interested in such a project, have a look at our current web site, and give me an informal time/cost estimate of migrating this website to use a [Python based? ;-)] CMS.   If we do proceed to tender, you would most likely be included in our list of firms contacted to submit an offer.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-1140155112427850645?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/1140155112427850645/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=1140155112427850645' title='12 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/1140155112427850645'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/1140155112427850645'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2007/10/choosing-cms-will-python-make-it.html' title='Choosing a CMS ... Will Python make it'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>12</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-1779478494821958826</id><published>2007-08-31T21:28:00.000-03:00</published><updated>2007-08-31T21:48:50.152-03:00</updated><title type='text'>pyglet and pygame: which one?</title><content type='html'>&lt;a href="http://www.pygame.org/"&gt;Pygame&lt;/a&gt; is great.  Pete Shinners has done some fantastic work in creating it.  I have played a bit with it in the past and was planning to learn to use it better, and probably write some &lt;a href="http://code.google.com/p/crunchy"&gt;Crunchy&lt;/a&gt;-based tutorials for it.  Unfortunately, this will not happen.  Since I moved from a PC to a Mac, and installed Python 2.5, I can not use Pygame, as there is no ready-made version for my setup.  Yes, I imagine I could find out how to compile it from the source ... but there is an easier alternative: &lt;a href="http://www.pyglet.org"&gt;pyglet&lt;/a&gt;.  pyglet is a pure python library, with no external dependency.  I downloaded it, tried it ... and it just worked.  Not only that, but when Python 2.6 will come out, I know it will still work.&lt;br /&gt;&lt;br /&gt;I do realise that pyglet is just in alpha stage ... but it's a very impressive alpha.  It is not as fully featured as Pygame is ... but it works for me, thanks to Alex Holkner.&lt;br /&gt;&lt;br /&gt;If you're currently using pygame, there's probably not any reason to switch.  But if, like me, you find that you can not use pygame with your current setup, check out pyglet.  You might be pleasantly surprised.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-1779478494821958826?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/1779478494821958826/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=1779478494821958826' title='9 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/1779478494821958826'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/1779478494821958826'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2007/08/pyglet-and-pygame-which-one.html' title='pyglet and pygame: which one?'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>9</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-541574066733683458</id><published>2007-08-31T15:03:00.001-03:00</published><updated>2007-08-31T18:36:44.057-03:00</updated><title type='text'>Python 3.0a1 has been released!</title><content type='html'>Congratulations to all Python developers on the &lt;a href="http://mail.python.org/pipermail/python-list/2007-August/455631.html"&gt;release of Python 3.0a1!&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;My favourite language is on its way to get even better!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-541574066733683458?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/541574066733683458/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=541574066733683458' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/541574066733683458'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/541574066733683458'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2007/08/python-30-has-been-released.html' title='Python 3.0a1 has been released!'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-3282524111065630562</id><published>2007-08-07T18:10:00.000-03:00</published><updated>2007-08-07T18:16:34.157-03:00</updated><title type='text'>Crunchy-like sites for javascript and ruby</title><content type='html'>While I have been aware of the &lt;a href="http://tryruby.hobix.com/"&gt;Try Ruby&lt;/a&gt; site for quite a while, recently I came across this &lt;a href="http://eloquentjavascript.net/contents.html"&gt;interactive javascript tutorial&lt;/a&gt;.  While none of these are nearly as flexible as Crunchy, they are nice resources that are currently available for anyone to try without needing to install anything.  It would be nice if I could have a dedicated server running Crunchy in a sandbox so that people could try it out without having to download it.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-3282524111065630562?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/3282524111065630562/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=3282524111065630562' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/3282524111065630562'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/3282524111065630562'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2007/08/crunchy-like-sites-for-javascript-and.html' title='Crunchy-like sites for javascript and ruby'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-5811701488743917143</id><published>2007-08-07T18:00:00.000-03:00</published><updated>2007-08-07T18:17:39.234-03:00</updated><title type='text'>flat is better than nested?</title><content type='html'>In the process of making Crunchy more secure, I had extra attention to css files that contained import statements, since these can be used to insert javascript code.  I was rather surprised when I looked at www.python.org.  First, I noticed the line&lt;br /&gt;&lt;pre id="line14"&gt;  &lt;&lt;span class="start-tag"&gt;link&lt;/span&gt;&lt;span class="attribute-name"&gt; rel&lt;/span&gt;=&lt;span class="attribute-value"&gt;"stylesheet" &lt;/span&gt;&lt;span class="attribute-name"&gt;type&lt;/span&gt;=&lt;span class="attribute-value"&gt;"text/css" &lt;/span&gt;&lt;span class="attribute-name"&gt;media&lt;/span&gt;=&lt;span class="attribute-value"&gt;"screen"&lt;br /&gt;&lt;/span&gt;&lt;span class="attribute-name"&gt;  id&lt;/span&gt;=&lt;span class="attribute-value"&gt;"screen-switcher-stylesheet" &lt;/span&gt;&lt;span class="attribute-name"&gt;href&lt;/span&gt;=&lt;span class="attribute-value"&gt;"/styles/screen-switcher-default.css" &lt;/span&gt;&lt;span class="error"&gt;&lt;span class="attribute-name"&gt;/&lt;/span&gt;&lt;/span&gt;&gt;&lt;/pre&gt;which, in itself, appears rather ordinary.  Upon following the link, I found that the content of the css file consists of the single line:&lt;br /&gt;&lt;pre&gt;@import url(../styles/styles.css);&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;In its default security mode, Crunchy just ignores any style information that contains "url(" in it.  As a result, when viewed using the default security mode for Crunchy, the python.org site's formatting is lost.&lt;br /&gt;&lt;br /&gt;I wonder why the link redirection is used...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-5811701488743917143?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/5811701488743917143/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=5811701488743917143' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/5811701488743917143'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/5811701488743917143'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2007/08/flat-is-better-than-nested.html' title='flat is better than nested?'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-8006291727846211934</id><published>2007-07-28T20:40:00.000-03:00</published><updated>2007-07-28T20:46:16.063-03:00</updated><title type='text'>Crunchy Security Advisory</title><content type='html'>&lt;span class="fixed_width"   style="font-family:Courier, Monospaced;font-size:100%;"&gt;A security hole has been uncovered in Crunchy (version 0.9.1.1 and earlier).&lt;br /&gt;&lt;/span&gt;&lt;p&gt;&lt;span class="fixed_width"   style="font-family:Courier, Monospaced;font-size:100%;"&gt;Anyone using Crunchy to browse web tutorials should only visit sites that are trustworthy.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span class="fixed_width"   style="font-family:Courier, Monospaced;font-size:100%;"&gt;We are working hard at fixing the hole; a new release addressing the problems that have been found should be forthcoming shortly.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span class="fixed_width"   style="font-family:Courier, Monospaced;font-size:100%;"&gt;-----&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span class="fixed_width"   style="font-family:Courier, Monospaced;font-size:100%;"&gt;The security problem is as follows:&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span class="fixed_width"   style="font-family:Courier, Monospaced;font-size:100%;"&gt;In theory, a web page could contain some javascript code (or link to such code) that would bypass Crunchy's filter to be executed by the browser.  If that is the case, the javascript code could be designed to send some Python code directly to the Python backend (i.e. without the Crunchy user pressing a button, or having the chance to view the code to be executed) so that it is executed.   Such code could result in deleting the entire files or installing some virus on the user's machine.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span class="fixed_width"   style="font-family:Courier, Monospaced;font-size:100%;"&gt;At the moment, the risk is pretty low.  Crunchy already removes all obvious (and most non-obvious) javascript code, links to such code, etc.  The holes found require the use of some uncommon combination of html and css code, with a particular knowledge of Firefox.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span class="fixed_width"   style="font-family:Courier, Monospaced;font-size:100%;"&gt;(Note that browsers other than Firefox are likely to be even more vulnerable).&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span class="fixed_width"   style="font-family:Courier, Monospaced;font-size:100%;"&gt;Furthermore, Crunchy is not that well known that it is likely to be a target by a cracker that would 1) write a "tutorial" interesting enough to lure current Crunchy users (who, at this point, are likely to include only advanced Python users) and 2) write some fairly involved javascript code to bypass the second security layer (where the commands enabling communication between the browser and crunchy are made up of random string generated uniquely at each new Crunchy  session).&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;span class="fixed_width"   style="font-family:Courier, Monospaced;font-size:100%;"&gt;If anyone is interested in security issues related to Crunchy, feel free to contact me directly. &lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-8006291727846211934?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/8006291727846211934/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=8006291727846211934' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/8006291727846211934'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/8006291727846211934'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2007/07/crunchy-security-advisory.html' title='Crunchy Security Advisory'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-938192359318883376</id><published>2007-07-10T21:08:00.000-03:00</published><updated>2007-07-10T21:17:14.166-03:00</updated><title type='text'>Rur-ple 1.0rc2 : bug fix for wxPython 2.8</title><content type='html'>Three changes; a grand total of 3 lines of code.  That's all I needed to change to make rur-ple work with wxPython 2.8 (and probably 2.7).   It only took me 8 months to get around to fix it due to a combination of lack of time and, when I had time, my obsession with Crunchy. &lt;br /&gt;&lt;br /&gt;Speaking of Crunchy, I found a bug today: any Python output that has something like&lt;br /&gt;&lt;div style="text-align: center;"&gt;&lt; ..... &gt;&lt;br /&gt;&lt;div style="text-align: left;"&gt;in it will be such that the stuff between angle brackets will not be shown.  This is because the browser thinks it is a valid tag.  The first time I noticed it was when I tried to do help(cmp).  Then, I was going through "how to think like a computer scientist" (before I point my son to it) and noticed that the exercise with type(...) did not work.  So, it is time to do a bug fix release - but no official announcement of this minor release.  I'm curious to see if I'll hear from someone about this bug prior to the next "major" release.&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-938192359318883376?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/938192359318883376/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=938192359318883376' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/938192359318883376'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/938192359318883376'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2007/07/rur-ple-10rc2-bug-fix-for-wxpython-28.html' title='Rur-ple 1.0rc2 : bug fix for wxPython 2.8'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-7743855352627545346</id><published>2007-07-09T20:52:00.000-03:00</published><updated>2007-07-09T20:59:41.582-03:00</updated><title type='text'>New Crunchy (0.9) is out</title><content type='html'>By now, the official announcement should have made it to many lists.  After a mad rush of coding and writing documentation over the past week or so, and implementing lots of new cool features, the new Crunchy is officially taking over from the old one.&lt;br /&gt;&lt;br /&gt;I believe that this new version can do much to promote Python usage.  If you do agree, please mention it!&lt;br /&gt;&lt;br /&gt;Here's a copy of the announcement I sent to various lists:&lt;br /&gt;&lt;br /&gt;Crunchy 0.9 has been released.  It is available at &lt;a href="http://code.google.com/p/crunchy"&gt;http://code.google.com/p/crunchy&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;What is Crunchy?&lt;br /&gt;&lt;br /&gt;Crunchy is a an application that transforms html Python tutorials into interactive session viewed within a browser.  We are not aware of any other application (in any language) similar to Crunchy.   Currently Crunchy has only been fully tested with Firefox; we know that some browsers simply don't work with it.  Crunchy should work with all operating systems - it has been tested fairly extensively on Linux, Windows and Mac OS.&lt;br /&gt;&lt;br /&gt;What is new in this release?&lt;br /&gt;&lt;br /&gt;Crunchy has been rewritten from scratch from the previous version (0.8.2), to use a custom plugin architecture.  This makes easier to extend and add new functionality.  Rather than list the differences with the old release, it is easier to list the essential features of this new version.&lt;br /&gt;&lt;br /&gt;1. Crunchy can work best with specially marked-up html tutorials. However, it can now work with any html tutorials - including the official Python tutorial on the python.org site.  Html pages can be loaded locally or remotely from anywhere on the Internet.  Crunchy uses a combination of Elementtree and BeautifulSoup to process html pages.  Non W3C-compliant pages can be handled, but the visual appearance is not guaranteed to reproduce that normally seen using a browser.&lt;br /&gt;&lt;br /&gt;2. Crunchy can insert a number of Python interpreters inside a web page.  In the default mode, it does that whenever it encounters an html [pre] element which is assumed to contain some Python code. These interpreters can either share a common environment (e.g. modules imported in one of them are known in the other) or be isolated one from another.&lt;br /&gt;&lt;br /&gt;3. Crunchy adds automatic css styling to the Python code -  you can look at the official Python tutorial using your browser (all Python code in blue) and compare with what Crunchy displays to give you a good idea.&lt;br /&gt;&lt;br /&gt;4. Instead of inserting an interpreter, Crunchy can insert a code editor that can be used to modify the Python code on the page and execute it.  The editor can be toggled to become a fairly decent syntax aware editor that can save and load files.&lt;br /&gt;&lt;br /&gt;5. Crunchy has a "doctest" feature where the code inside the [pre] is taken to be the result of an interpreter session and the user has to write the code so as to make the interpreter session valid; this is useful in a teaching environment. Messages from the Crunchy's doctest are "friendlier" for Python beginners than the usual tracebacks.&lt;br /&gt;&lt;br /&gt;6. Crunchy has a small graphics library that can be imported, either inside an editor or an interpreter, to produce simple graphics (even animations!) inside the browser.&lt;br /&gt;&lt;br /&gt;7. For the user that needs better quality graphics, Crunchy supports programs (such as matplotlib) that can create image files; by executing the code, the image produced is loaded inside the browser window.  In this capacity, Crunchy could be used as a front end for libraries such as matplotlib.&lt;br /&gt;&lt;br /&gt;8. Crunchy supports code execution of files as separate processes, making it suitable to launch gui based application from the browser window.&lt;br /&gt;&lt;br /&gt;9. Crunchy's interpreter has an interactive "help" feature like many python-aware IDEs.&lt;br /&gt;&lt;br /&gt;10. Crunchy includes a fairly comprehensive tutorial on its own use, as well as a reference for tutorial writers that want to make their tutorials "crunchy-friendlier".&lt;br /&gt;&lt;br /&gt;11. As a security feature, crunchy strips all pre-existing javascript code from an html page before displaying it inside the browser window.&lt;br /&gt;&lt;br /&gt;Bug reports, comments and suggestions are always welcome.&lt;br /&gt;&lt;br /&gt;André Roberge, for the Crunchy team.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-7743855352627545346?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/7743855352627545346/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=7743855352627545346' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/7743855352627545346'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/7743855352627545346'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2007/07/new-crunchy-09-is-out.html' title='New Crunchy (0.9) is out'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-235200887082753615</id><published>2007-06-24T22:54:00.000-03:00</published><updated>2007-06-24T23:06:30.186-03:00</updated><title type='text'>Fun with the new Crunchy</title><content type='html'>Since the new Crunchy can work with files that have no Crunchy-specific additional markup, some neat experiments are possible, in addition to playing with the official Python tutorial with a Python interpreter embedded on the web page that I mentioned in my last post.  For the curious among you, here's something else to try.&lt;br /&gt;&lt;br /&gt;Assuming you have the latest (version 0.2) alpha release of the new Crunchy, edit the file vlam.py, replacing "interpreter" by "editor" at lines 108 and 109.  Then, launch Crunchy and click on the "tests" link.  On the following page, click on the "Loading arbitrary tutorials" link.  Then, in the box for loading remote tutorials, enter the address of the Python cookbook (http://aspn.activestate.com/ASPN/Python/Cookbook/).&lt;br /&gt;The formatting will be off, but you can select your favorite recipe.  When you do this, you can edit the code and execute it right on the page.  Often, all you need to do is to replace &lt;code&gt; __name__ == "__main__"&lt;/code&gt; by &lt;code&gt;True&lt;/code&gt; and you are ready to try the examples, modifying them at will.&lt;br /&gt;&lt;br /&gt;In the near future, Crunchy will provide an easy way to select which interactive element (Python interpreter or editor) is to be inserted by default, without having to edit the code by hand.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-235200887082753615?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/235200887082753615/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=235200887082753615' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/235200887082753615'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/235200887082753615'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2007/06/fun-with-new-crunchy.html' title='Fun with the new Crunchy'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-9180444440276031794</id><published>2007-06-24T16:21:00.001-03:00</published><updated>2007-06-24T20:06:50.307-03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='crunchy'/><title type='text'>From vlam to nam: Using Crunchy to interact with the official Python tutorial</title><content type='html'>&lt;span style="font-weight: bold;"&gt;Breaking news:&lt;/span&gt; you can now use Crunchy to browse and interact with the official Python tutorial.  &lt;span style="font-style: italic;"&gt;More below...&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Since my last &lt;a href="http://aroberge.blogspot.com/2007/03/new-crunchy-engine.html"&gt;post&lt;/a&gt; that described briefly some features of the new Crunchy engine, I have not had much time to work on Crunchy.  Johannes too has been fairly busy so that Crunchy development pretty much stopped, until the beginning of Google Summer of Code.  Two students joined the Crunchy team: &lt;a href="http://pythonnez-moi.blogspot.com/"&gt;Edin Salković&lt;/a&gt; and &lt;a href="http://voxelz.net/"&gt;Bryan Psimas&lt;/a&gt;.  Unfortunately, due to other commitments, Edin was not able to continue with his SoC project - but he did manage to write the prototype of a new plugin for Crunchy that I will describe below.&lt;br /&gt;&lt;br /&gt;The new Crunchy engine now works extremely well.  As I wrote in my previous post,  Crunchy is now more responsive; it supports simultaneous execution of multiple code sample, proper input (stdin) handling from the webpage browser and the api (for writing tutorials) has been significantly simplified as there is no longer be any need to have an embedded "canvas" call in a tutorial: the user is now able to load a graphics dynamically anywhere on a page.  These graphics can even include simple animations using pure Python code (for the end user - javascript behind the scene for us, unfortunately).&lt;br /&gt;&lt;br /&gt;There is more.  For example:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;it is now possible to style &amp;lt;code&amp;gt; elements;&lt;/li&gt;&lt;li&gt;Python code sample (either inside &amp;lt;pre&amp;gt; or &amp;lt;code&amp;gt;) that contain pre-existing html markup can now be handled by Crunchy without generating an error (blank page!) as before;&lt;/li&gt;&lt;li&gt;it is now possible to specify a starting number different from 1 when requesting that line numbers be added to a code sample;&lt;/li&gt;&lt;li&gt;when it styles the code, Crunchy now automatically detects if a given code sample represents an interpreter session or a "normal" code sample;&lt;/li&gt;&lt;li&gt;it is now possible to use input() and raw_input() with an interpreter, and the result appears inside the page displayed by the browser.&lt;/li&gt;&lt;/ul&gt;In addition, thanks to Edin's work, a new type of interaction with Python code has been added: it is possible to save the result of a computation in a graphics file and display the result in the browser.  This has been used successfully with matplotlib.  There is a "small" bug in that the Python code needs to be executed twice due to synchronization issues with the browser loading the file and Python saving it.&lt;br /&gt;&lt;br /&gt;Quite a few features from the  "old Crunchy" need to be implemented (e.g. menus including a way to properly quit the application, translations, &lt;i&gt;update:&lt;/i&gt;&lt;strike&gt;the ability to load an arbitrary tutorials given a URL&lt;/strike&gt;, etc.) in the new version but, overall, it is working very well.&lt;br /&gt;&lt;br /&gt;One of the "problems" with the old Crunchy is that it did require two things from a tutorial writer:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;That the html code written be W3C compliant (with no warning, no unclosed tags, etc.).&lt;/li&gt;&lt;li&gt;That Crunchy specific markup (vlam = very little additional markup) be added to instruct Crunchy to add interactive elements.&lt;/li&gt;&lt;/ol&gt;Unfortunately, the &lt;a href="http://docs.python.org/tut/tut.html"&gt;official Python tutorial&lt;/a&gt; fails on both accounts.  However, with the use of a version of BeautifulSoup adapted by effbot (thanks!) to be used with ElementTree, the new Crunchy can now deal with non W3C compliant code.&lt;br /&gt;&lt;br /&gt;And ... Crunchy has now been written so that it automatically inserts a Python interpreter whenever it sees a bare &amp;lt;pre&amp;gt; (i.e. with nam = no additional markup).  As a result, one can now browse the official Python tutorial with Crunchy and interact with it.&lt;br /&gt;&lt;br /&gt;This automatic insertion of an interpreter sometimes yields too many interpreter inserted on a page than really needed; it is better to hand craft a tutorial.  However, it should make Crunchy a lot more useful to many more people.&lt;br /&gt;&lt;br /&gt;For those interested in trying out the "new Crunchy", an alpha release is &lt;a href="http://code.google.com/p/crunchy/"&gt;available&lt;/a&gt;.  If you have never used Crunchy before, you should try version 0.8.2 first and go through the tutorial from the menu.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-9180444440276031794?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/9180444440276031794/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=9180444440276031794' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/9180444440276031794'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/9180444440276031794'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2007/06/from-vlam-to-nam-using-crunchy-to.html' title='From vlam to nam: Using Crunchy to interact with the official Python tutorial'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-5759002379727118027</id><published>2007-03-06T20:21:00.000-04:00</published><updated>2007-03-06T20:44:48.488-04:00</updated><title type='text'>New Crunchy engine</title><content type='html'>At the end of my talk at Pycon 2007, I gave an extremely brief demo of the new (not in production yet) upcoming version of Crunchy.  Thanks to the work of Johannes Woolard, Crunchy's javascript core engine has been completely rewritten.  As a result, Crunchy will be more responsive; it will also support simultaneous execution of multiple code sample, proper input (stdin) handling from the webpage browser and the api (for writing tutorials) will be greatly simplified as there will no longer be any need to have an embedded "canvas" call in a tutorial: the user will be able to load a graphics dynamically anywhere on a page.&lt;br /&gt;&lt;br /&gt;But that is not all...&lt;br /&gt;&lt;br /&gt;Inspired by a side remark by Ivan Krstić (of OLPC fame) about finding the best way to design plugins in Python, I contacted Johannes to discuss the idea of using plugins to extend Crunchy.  After a few back and forth emails, while I was on the road on business trips, Johannes just went ahead and implemented a first (then a second...) way of using plugins to extend Crunchy.  In the end, it looks like we'll have to rewrite almost completely (like we did during the Summer of Code 2006) Crunchy's code as we move from one minor version (0.8) to the next (0.9).  However, the result will be well worth it.&lt;br /&gt;&lt;br /&gt;The unstated goal: to make it (almost) as easy for developpers to add new capabilities to Crunchy as it is for tutorial writer to use Crunchy to create interactive tutorials.  Ok, this might be a slight exaggeration ... but not much of one ;-)  And most of this will be the result of Johannes' great work.  So, for all of those employers (and there were many at Pycon) looking for a brilliant Python programmer to hire over the summer, I know of an Oxford student who certainly fits the bill.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-5759002379727118027?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/5759002379727118027/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=5759002379727118027' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/5759002379727118027'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/5759002379727118027'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2007/03/new-crunchy-engine.html' title='New Crunchy engine'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-7243080470924677702</id><published>2007-02-26T19:49:00.000-04:00</published><updated>2007-02-26T20:12:42.114-04:00</updated><title type='text'>Pycon experience</title><content type='html'>Pycon 2007 is now officially over.  It was very nice to be able to associate faces to so many familiar names from the Python community and get the opportunity to talk with a few of them.  &lt;br /&gt;&lt;br /&gt;My talk didn't go as smoothly as I had hoped.  When I practiced it, it seemed to always take too long.  So, when I had to do it in front of a fairly large crowd, I rushed through it a bit too quickly.  Still, Crunchy did seem to generate a fair bit of interest and, in addition to the demo in my talk, I got the opportunity to demonstrate it "one-on-one" to about a dozen people who all seemed to be very keen.&lt;br /&gt;&lt;br /&gt;Also, the idea of creating a special Python logo ("web badge") for identifying Crunchy-ready tutorials, which I had alluded to in a previous post, will likely be given the ok by the PSF.  As soon as it is confirmed, I'll make an announcement here.&lt;br /&gt;&lt;br /&gt;Since my last post, the PEP I wrote for keeping raw_input()'s functionality in Python 3000 [but renaming it "input()"] had been accepted.  On Monday, I took part briefly in a Python 3000 sprint where I got to implement the required "raw_input(...) -&gt; input(...)" and "input(...) -&gt; eval(input(...))" conversion tool for the general tool that will be available to convert Python 2.x programs so that they work with Python 3k.  I also implemented the corresponding simple unit tests as well a few missing tests for the "xrange() -&gt; range" conversion.  I would not have been able to do this if Guido van Rossum had not taken the time to explain a few things to me about the conversion framework.  In the end, Guido checked in my changes himself.  I get the feeling that he could have done the entire thing in about the same time it took him to explain to me the tricky bits.   Nonetheless, I do get the satisfaction of having, with the PEP and the conversion tool, given back a bit to the Python community.  Hopefully, with Crunchy, I (and Johannes, of course) will give back even more.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-7243080470924677702?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/7243080470924677702/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=7243080470924677702' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/7243080470924677702'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/7243080470924677702'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2007/02/pycon-experience.html' title='Pycon experience'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-2712606595636084132</id><published>2007-02-12T19:58:00.000-04:00</published><updated>2007-02-09T20:10:11.131-04:00</updated><title type='text'>Switched to Mac OS</title><content type='html'>I'm a statistics... I just got a Macbook and will likely use it as my main development platform soon.  So far, moving from Windows XP to Mac OS has been fairly painless.  I have not done any Python programming but did try out Crunchy and Rur-ple.  I found out that Crunchy requires Python 2.4+ (I had 2.3.5 installed on the Mac), that it does not work with Safari, and that there is a "bug" so that the "tooltip" does not work with Firefox on the Mac.  I was planning to bring my Macbook to Pycon but will have to reevaluate as I'd like the Drunchy demo to go as smoothly as possible.  There also does not appear to be a sound module on the Mac similar to winsound or ossaudiodev...  I also found a new bug with rur-ple, but have no time to look into it before Pycon.&lt;br /&gt;&lt;br /&gt;Not having taken side in the emacs/vim religious war, I have to find a decent programming environment.  On my PC, I used SPE which I really liked but I have read a lot of good things about textmate and nothing negative (other than the fact that it is not free).  Any advice from Mac/Python users?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-2712606595636084132?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/2712606595636084132/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=2712606595636084132' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/2712606595636084132'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/2712606595636084132'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2007/02/switched-to-mac-os.html' title='Switched to Mac OS'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-1269797951653173250</id><published>2007-02-05T23:46:00.000-04:00</published><updated>2007-02-06T00:01:29.473-04:00</updated><title type='text'>Rur-ple segmentation fault on Ubuntu: solved?</title><content type='html'>Some kind readers have left comments on this blog regarding segmentation faults occurring when trying to run Rur-ple using Ubuntu linux.   I finally got around to installing a vmware version of ubuntu so that I could check for myself and believe I found the source of the bug.  I would appreciate if someone actually using Ubuntu could verify.&lt;br /&gt;&lt;br /&gt;Near the bottom of rur_start.py, a splash screen is started with 100 (milliseconds) as a default.  Changing that number to 1000 removed the segmentation fault when running rur-ple in a virtual ubuntu environment.&lt;br /&gt;&lt;br /&gt;There is at least one other bug in rur-ple when using a version of wxPython greater than 2.6; it is in lightning.py.  Changing "event.KeyCode()" to "event.GetKeyCode()" apparently solves the problem.&lt;br /&gt;&lt;br /&gt;Of course, I would be interested in hearing about any other bugs that have been encountered so that I could do a final cleanup before finally releasing the 1.0 version.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-1269797951653173250?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/1269797951653173250/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=1269797951653173250' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/1269797951653173250'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/1269797951653173250'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2007/02/rur-ple-segmentation-fault-on-ubuntu.html' title='Rur-ple segmentation fault on Ubuntu: solved?'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-6200820894234711162</id><published>2007-01-31T16:55:00.000-04:00</published><updated>2007-01-31T19:20:58.753-04:00</updated><title type='text'>Lightning talk?</title><content type='html'>Living in a small village in Nova Scotia, 300 km from the nearest city (Halifax, pop.: 380,000), the opportunities to discuss face to face with other pythonistas are non-existent. This is one reason I've been really looking forward to going to my first &lt;a href="http://us.pycon.org/TX2007/HomePage"&gt;Pycon&lt;/a&gt;. In addition to my &lt;a href="http://us.pycon.org/apps07/talks/?filter=tutorial"&gt;scheduled talk&lt;/a&gt;, I have been thinking about giving a lightning talk of a different kind, but still touching upon a subject dear to me, that is the promotion of Python and its use as a teaching language.  While lightning talks are so short that they normally touch upon only one topic, I was thinking of actually covering three topics, under the common theme of "interactive tutorials".  These three topics are, from the more serious to the semi-frivolous:&lt;br /&gt;&lt;br /&gt;1. The fate of input() and raw_input() in Python.&lt;br /&gt;2. Using a distinctive logo for promoting interactive tutorials using Python.&lt;br /&gt;3. Creating an interactive tutorial repository.&lt;br /&gt;&lt;br /&gt;According to &lt;a href="http://www.python.org/dev/peps/pep-3100/"&gt;this&lt;/a&gt;, both input() and raw_input() are supposed to disappear in Python 3.0.  After thinking about it for quite some time, I &lt;a href="http://mail.python.org/pipermail/edu-sig/2006-September/006967.html"&gt;posted a message on edu-sig&lt;/a&gt; about the proposed change last September, expressing my "opposition" to the proposed change, and seeking the opinion of others on this topic.  After a while, a near-consensus emerged, supporting the idea of keeping at least something like raw_input() in Python 3.0.  I then wrote a &lt;a href="http://mail.python.org/pipermail/edu-sig/2006-September/007146.html"&gt;pre-PEP&lt;/a&gt;, which generated more positive comments and encouragements but otherwise no further action from other edu-sig subscribers more involved in the Python community than I am.  Unfortunately, due to my work, I had to drop all my Python-related activities for a few months and was not able to follow up until late December.  With the end of the year approaching, I found the time to incorporate the comments received on edu-sig and offered a revised pre-PEP to the Python 3000 list on &lt;a href="http://mail.python.org/pipermail/python-3000/2006-December/005242.html"&gt;December 22nd&lt;/a&gt;.  This was a single posting from me as I don't subscribe to that list.  A bit of discussion ensued after which &lt;a href="http://mail.python.org/pipermail/python-3000/2006-December/005249.html"&gt;Guido stated&lt;/a&gt; that he like the proposal better than any of the other alternatives mentioned  and, later, &lt;a href="http://mail.python.org/pipermail/python-3000/2006-December/005257.html"&gt;suggested&lt;/a&gt; that someone should "&lt;span style="font-style: italic;"&gt;clean up and check in the proto-PEP and start working on an implementation or patch&lt;/span&gt;."&lt;br /&gt;&lt;br /&gt;Unfortunately, and perhaps because this happened just before the holidays, nothing further was done.  So, I'm thinking about raising the issue again at Pycon.  Having something like raw_input() is, I believe, nearly essential for using interactive tutorials of the kinds produced by crunchy in a teaching environment aimed at beginners.&lt;br /&gt;&lt;br /&gt;Further on the topic of interactive tutorials, I have been thinking of an easy means to identify them.  For those that have tried crunchy, you will have noticed that an interactive tutorial viewed through a regular browser without using crunchy appears to be nothing else than your average (W3C compliant) html document.  Since such tutorials could be included on any web site, I was thinking it might be useful to give a visual clue to crunchy users.  There already exist a Python logo to identify applications, namely:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_KpSgL5zKAvE/RcEKKQStacI/AAAAAAAAAAM/FD4Ttdvw2xY/s1600-h/python-powered-w-100x40.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp1.blogger.com/_KpSgL5zKAvE/RcEKKQStacI/AAAAAAAAAAM/FD4Ttdvw2xY/s200/python-powered-w-100x40.png" alt="" id="BLOGGER_PHOTO_ID_5026309830410594754" border="0" /&gt;&lt;/a&gt;I've been thinking of asking the Python software foundation for the permission to modify this logo, replacing the word "powered" by "interactive", and adding a small change to crunchy so that every tutorial created with it would automatically have the logo added at the top.  I'm debating whether I should do this prior to Pycon, or wait until I get the chance to lobby for it in person.&lt;br /&gt;&lt;br /&gt;Finally, if enough people start writing interactive tutorials using crunchy, I thought it might be useful to have a central repository somewhere.  Moving away from the Monty Python references, I thought that the Snake PIT (for Python Interactive Tutorials) might be a catchy name.&lt;br /&gt;&lt;br /&gt;So, for you, patient reader that has made it this far, does it sound like a worthwhile for a Lightning talk, especially if it is supplemented by a one-minute demo of an interactive tutorial?  I'm pretty sure I could cover all three topics above in less than the scheduled 5 minutes for a lightning talk, as I would not have to go into much detail about the history; just the facts...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-6200820894234711162?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/6200820894234711162/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=6200820894234711162' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/6200820894234711162'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/6200820894234711162'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2007/01/lightning-talk.html' title='Lightning talk?'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp1.blogger.com/_KpSgL5zKAvE/RcEKKQStacI/AAAAAAAAAAM/FD4Ttdvw2xY/s72-c/python-powered-w-100x40.png' height='72' width='72'/><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-2832434514352947473</id><published>2007-01-27T18:16:00.000-04:00</published><updated>2007-01-27T18:23:34.926-04:00</updated><title type='text'>Crunchy 0.8 is out</title><content type='html'>Version 0.8 of Crunchy has been released.  It is available at its new home on &lt;a href="http://code.google.com/p/crunchy/"&gt;code.google.com&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Crunchy, the Interactive Python Tutorial Maker, is an application that&lt;br /&gt;transforms an ordinary html-based Python tutorial into an interactive&lt;br /&gt;session within a web browser.  Currently, only Firefox is supported.&lt;br /&gt;Crunchy is developed and tested on Windows XP and Ubuntu Dapper Drake,&lt;br /&gt;but should work on any suitable windows or UNIX system.&lt;br /&gt;&lt;br /&gt;Three major improvements have been made since version 0.7 had been released.&lt;br /&gt;&lt;br /&gt;1. New editor&lt;br /&gt;&lt;br /&gt;Instead of a simple html textarea, Crunchy now gives the option of&lt;br /&gt;using a "real" editor, namely &lt;a href="http://www.cdolivet.net/editarea/"&gt;EditArea&lt;/a&gt;.  EditArea support syntax coloring&lt;br /&gt;and allows loading and saving local Python files among other features.&lt;br /&gt; Within Crunchy, it is set up so that the tab key is translated into 4&lt;br /&gt;spaces.&lt;br /&gt;&lt;br /&gt;2. Language support&lt;br /&gt;&lt;br /&gt;Crunchy now supports English and French, through the use of ".po"&lt;br /&gt;files.  When running Python code, some error messages have been&lt;br /&gt;adapted/translated.  EditArea itself support more languages&lt;br /&gt;(currently: Danish, Dutch, English, French, German, Italian, Japanese,&lt;br /&gt;Polish, Portuguese).&lt;br /&gt;&lt;br /&gt;3. Graphical tutorial converter.&lt;br /&gt;&lt;br /&gt;Crunchy uses some supplementary markup to transform html files into&lt;br /&gt;interactive tutorials.  Whereas previous versions required a tutorial&lt;br /&gt;maker to edit an html file "by hand", version 0.8 includes a tutorial&lt;br /&gt;editor: with a few clicks, you can easily add to an html file the&lt;br /&gt;chosen interactive elements and options for Crunchy.&lt;br /&gt;&lt;br /&gt;In addition to the above major improvements, the code has been&lt;br /&gt;refactored significantly and a number of small bug fixes have been&lt;br /&gt;made.  Crunchy will be demonstrated at the upcoming &lt;a href="http://us.pycon.org/apps07/talks/?filter=education"&gt;Pycon 2007&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The next release will likely have a new, simplified API for tutorial writers, but with more powerful features, thanks to the work of Johannes Woolard.  Unfortunately, it is unlikely to be ready in time for Pycon.  Anyone planning to go to Pycon, and who is interested in Crunchy should feel free to contact me with any questions/suggestions they may want to have me address during my presentation.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-2832434514352947473?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/2832434514352947473/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=2832434514352947473' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/2832434514352947473'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/2832434514352947473'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2007/01/crunchy-08-is-out.html' title='Crunchy 0.8 is out'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-5125439852860288692</id><published>2007-01-10T17:35:00.000-04:00</published><updated>2007-01-10T17:49:37.652-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='crunchy'/><category scheme='http://www.blogger.com/atom/ns#' term='pycon'/><title type='text'>Pycon 2007: it's Crunch time - take 2</title><content type='html'>Well, it's done: I've registered for Pycon, booked a hotel room and bought plane tickets.  Since November 1, when I first heard that my talk had been accepted, &lt;a href="http://crunchy.sourceforge.net/"&gt;crunchy &lt;/a&gt;has changed quite a bit and my planned presentation will have to change.  One demo I had planned to do, using &lt;a href="http://www.codetch.com/"&gt;codetch&lt;/a&gt;, and which I thought would take about 5 minutes will now be doable in less than 30 seconds without having to use anything else than the "new and improved" crunchy.  Unfortunately, there has been so much changed since the last release that the documentation has to be re-organized significantly - this means that I can't really do a new release right now as it would likely be too confusing. So, I'm racing against the clock to put everything together to do a new release before Pycon. &lt;span style="font-size:78%;"&gt;However, if some of you are interested in having a preview, drop me an email and I'll let you know where you can get the code from and give a quick description of how to use the new features.&lt;/span&gt;&lt;br /&gt;Looking at the &lt;a href="http://us.pycon.org/TX2007/Planning"&gt;official statistics&lt;/a&gt;, I find the relatively small numbers of attendees compared with last year somewhat disappointing.  Given that more talks are going to be presented this year, with a much larger rejection rate, I find this rather puzzling.  I imagine the organizers are rather disappointed.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-5125439852860288692?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/5125439852860288692/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=5125439852860288692' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/5125439852860288692'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/5125439852860288692'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2007/01/pycon-2007-its-crunch-time-take-2.html' title='Pycon 2007: it&apos;s Crunch time - take 2'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-4015554899208509527</id><published>2007-01-03T23:37:00.000-04:00</published><updated>2007-01-04T00:21:37.179-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='crunchy'/><category scheme='http://www.blogger.com/atom/ns#' term='i18n'/><title type='text'>Unicode headaches ... and a solution</title><content type='html'>Work on &lt;a href="http://crunchy.sourceforge.net/"&gt;Crunchy&lt;/a&gt; has restarted by both Johannes and I over the holidays, after a few months long hiatus. Hopefully, we'll be in a position to do a new release soon (&lt;span style="font-style: italic;"&gt;just a few more features...&lt;/span&gt;).  Among the changes, Crunchy now has a proper embedded editor, &lt;a href="http://www.cdolivet.net/editarea/"&gt;EditArea&lt;/a&gt;.  See this &lt;a href="http://www.cdolivet.net/editarea/editarea/exemples/exemple_full.html"&gt;example&lt;/a&gt; to get an idea of what EditArea can do.  Actually, the Python support has since been improved by Christophe Dolivet, the creator of EditArea, prompted by a few suggestions of my own, but the new version has not been made publicly available yet.  It is however already used in the development version of Crunchy, and, with a few additions of my own, will definitively be showcased during my Pycon presentation.&lt;br /&gt;&lt;br /&gt;Other Crunchy changes include a proper handling of English and French translations.  Now, since EditArea's tooltips are available in many more language, I thought I should use a more complete encoding (like utf-8) rather than the one I normally use (latin-1).   After all, if &lt;a href="http://rur-ple.sourceforge.net/"&gt;rur-ple&lt;/a&gt; has been found useful enough to be &lt;a href="http://aroberge.blogspot.com/2006/11/rur-ple-double-number-of-languages.html"&gt;adapted to 6 languages&lt;/a&gt; (with Italian and Chinese in the pipeline), I figured that Crunchy is likely to eventually see the same kind of adaptation.&lt;br /&gt;&lt;br /&gt;However, I soon encountered a most puzzling bug.  All strings translated were properly rendered by Firefox except for those coming out of an interpretor session (some tracebacks have been customized and translated in French as well as being available in English).  When French was selected as the default language, whenever an accented letter, like à, was supposed to appear as a result of a Python output, a ? instead was displayed.&lt;br /&gt;&lt;br /&gt;After trying all kind of encoding/decoding tricks over the course of a few hours, I remembered that &lt;a href="http://aroberge.blogspot.com/2006/01/note-to-self-about-site-customization.html"&gt;I had set the default sytem encoding on my computer to latin-1&lt;/a&gt;.  I decided to change it to utf-8 and, sure enough, everything was working as expected.  Success at last!&lt;br /&gt;&lt;br /&gt;However, this was only the beginning of my problems.  My favourite editor, SPE, stopped working.  I also tried to run rur-ple which failed miserably.  [Idle, on the other hand, which I use very rarely, was still working perfectly.]  Clearly, changing the site default encoding was not an appropriate solution: I could certainly not depend on having a crunchy user set his or her site customization to utf-8.  A different approach was needed.&lt;br /&gt;&lt;br /&gt;After reverting back to latin-1 as my default python site encoding (so that paths that included my name, André, were properly read by SPE) and poring over the code, I finally figured out a more general solution.&lt;br /&gt;&lt;br /&gt;Whenever Crunchy executes some Python code written by a user (or provided by a tutorial writer), it starts a Python interpreter in a separate thread.  This Python interpreter uses the default system encoding for all its computation.  When the result needs to be sent back by the Crunchy server to Firefox, it needs to have its encoding changed as&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;result = result.decode(sys.getdefaultencoding()).encode('utf-8')&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Of course, in retrospect, it all makes sense but it did stump me for quite a few hours; perhaps the information included here will save a few minutes or hours to someone else.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-4015554899208509527?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/4015554899208509527/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=4015554899208509527' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/4015554899208509527'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/4015554899208509527'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2007/01/unicode-headaches-and-solution.html' title='Unicode headaches ... and a solution'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-7125522602460863280</id><published>2006-11-30T19:23:00.000-04:00</published><updated>2006-11-30T19:29:18.684-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='rur-ple'/><title type='text'>Rur-ple 1.0rc</title><content type='html'>&lt;a href="http://rur-ple.sourceforge.net"&gt;Rur-ple&lt;/a&gt; 1.0 is finally out ... sort of.  Actually, it's a Release Candidate version.  For the next little while, any free time I'll have is going to be spent on preparing my &lt;a href="http://us.pycon.org/TX2007/HomePage"&gt;Pycon &lt;/a&gt;presentation (and working on &lt;a href="http://crunchy.sourceforge.net"&gt;Crunchy &lt;/a&gt;as needed to improve it before Pycon 2007).   Since I had three more localizations available (German, Turkish and Welsh) than there was on sourceforge, I decided that it was better to release early.  In the meantime, someone is working on an Italian localization (and possible lessons translation) which should be included in the final 1.0 version.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-7125522602460863280?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/7125522602460863280/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=7125522602460863280' title='14 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/7125522602460863280'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/7125522602460863280'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2006/11/rur-ple-10rc.html' title='Rur-ple 1.0rc'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>14</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-5468403098652960701</id><published>2006-11-30T18:13:00.000-04:00</published><updated>2006-11-30T18:31:09.617-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='crunchy'/><category scheme='http://www.blogger.com/atom/ns#' term='pycon'/><title type='text'>Pycon 2007: it's Crunch time</title><content type='html'>Yesterday I got some good news from the Pycon organizers: my proposed talk entitled &lt;span style="font-style: italic;"&gt;Easy creation of interactive tutorials&lt;/span&gt; has been accepted.  In this talk, I'll demonstrate how &lt;a href="http://crunchy.sourceforge.net/"&gt;Crunchy&lt;/a&gt; can be used to create interactive Python tutorials.  I've been thinking about how to organize my talk for about two months now but have not written a line yet.  One thing I can say for sure at this time: this is not going to be your typical PowerPoint-type presentation.  My current plan is to use Firefox for the presentation: one tab will display a fake html-based Python tutorial, the second tab will make use of &lt;a href="http://www.codetch.com/"&gt;codetch &lt;/a&gt;so that I can edit the same tutorial "live", and the third tab will display the resulting interactive tutorial as processed by Crunchy.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-5468403098652960701?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/5468403098652960701/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=5468403098652960701' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/5468403098652960701'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/5468403098652960701'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2006/11/pycon-2007-its-crunch-time.html' title='Pycon 2007: it&apos;s Crunch time'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-7792638496199351240</id><published>2006-11-13T18:15:00.000-04:00</published><updated>2006-11-13T18:28:31.315-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='rur-ple'/><title type='text'>Rur-ple: double the number of languages</title><content type='html'>While I haven't had time to do programming in the past few months, some Rur-ple users have been busy and submitted new material. The upcoming 1.0 release of Rur-ple will include 3 new languages [Turkish (including a translation of most lessons), German and Welsh] as well as previously included languages [English (with all lessons), French and Spanish].&lt;br /&gt;&lt;br /&gt;Inspired by the user contributions, I spent a few hours today re-writing the localization code.  To add a new language now only requires the following:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Adding &lt;span style="font-weight: bold; font-style: italic;"&gt;one&lt;/span&gt; line in a Python file (translation.py), that specify the language and language code, as well as the name of the .po file.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Creating a .po file containing the string translations.&lt;/li&gt;&lt;li&gt;Creating an html file (rur.htm) used as a Welcome Page in that language.&lt;/li&gt;&lt;/ol&gt;The new version will also save the last language selected and will use it upon re-starting Rur-ple.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-7792638496199351240?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/7792638496199351240/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=7792638496199351240' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/7792638496199351240'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/7792638496199351240'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2006/11/rur-ple-double-number-of-languages.html' title='Rur-ple: double the number of languages'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-115621225155932289</id><published>2006-08-21T22:37:00.000-03:00</published><updated>2006-11-13T18:10:10.039-04:00</updated><title type='text'>Securing Crunchy</title><content type='html'>Following my last post, some alert readers pointed out that Crunchy could be a security risk for its users.  Crunchy acts as a web server, fetching html-based Python tutorials and displaying them in your browser with controls allowing to execute the Python code.  This is meant as a good thing... but it might not be if the code is allowed to run automatically when the page is loaded.  In fact, this might have occurred with previous (&amp;lt;0.7) &lt;a href="http://pytute.blogspot.com/"&gt;Johannes&lt;/a&gt; and I changed the way that Crunchy works so as to remove any security worries - at least, we hope so.  Anyone interested can obviously look at the code; for others, here's what we do:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Each time Crunchy starts, it generates a random session number (10 digit long).&lt;/li&gt;&lt;li&gt;The javascript code used to communicate between Crunchy and the browser is fetched from a static file and modified to include the unique session number as part of each command; a new javascript file, with a filename that incorporates the session number is generated.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;When Crunchy fetches an html page, it processes it to remove unwanted code.  This means removing all (java-)scripts and only allowing html tags that are on a "whitelist".  This whitelist excludes any of the usual "onload", "oninit", etc., automatic execution.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Next, Crunchy interactive elements (Python interpreter, code editor, button for Python code execution, etc.) are inserted with the javascript calls, each call given a unique identifier for that session.&lt;/li&gt;&lt;li&gt;Finally, the processed page is displayed in the browser, with the server waiting for user-generated interactions.&lt;/li&gt;&lt;li&gt;This procedure is repeated for any page that is loaded during that session.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;At the end of a session, when Crunchy is terminated, the javascript file that was generated and used in that session is deleted.&lt;/li&gt;&lt;/ul&gt;The &lt;a href="http://sourceforge.net/project/showfiles.php?group_id=169458"&gt;latest version&lt;/a&gt; (0.7) includes this security features and many new additions [a complete list of the changes from version 0.6 is appended at the end of this message].  Currently, Johannes is working on adapting &lt;span style="font-style: italic;"&gt;How to think like a computer scientist&lt;/span&gt; to use with Crunchy, as well adding new interactive features.  For my part, I have been working on internationalisation issues (adding a French interface) and other configuration choices.  Due to end-of-summer holidays, work on Crunchy will likely slow down for a few weeks at least but we should be able to reach version 1.0 early in the Fall.&lt;br /&gt;&lt;br /&gt;After that, I will be ready to start implementing rur-ple inside the Crunchy environment; if there is enough interest, I might implement a turtle graphics module as well.&lt;br /&gt;&lt;br /&gt;===== Changes from 0.6 to 0.7 ================&lt;br /&gt;0. Name change&lt;br /&gt;To prevent confusion with an existing program named CrunchyFrog, "Crunchy Frog" has been renamed as "Crunchy", short for "Crunchy, the Interactive Python Tutorial Maker".&lt;br /&gt;&lt;br /&gt;1. *Security fix*&lt;br /&gt;The previous versions of Crunchy allowed tutorials containing arbitrary (and hidden) javascript code to be loaded within a browser window.  The new version removes any existing javascript code prior to processing for display within a browser.&lt;br /&gt;&lt;br /&gt;Feel free to try and break this - and please report any findings back to us.&lt;br /&gt;&lt;br /&gt;2. New functionality.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;It is possible to run external programs from within the browser; sample demos include GUI programs with 1. Tkinter, 2. pyGtk, 3. wxPython, 4. Pygame &lt;/li&gt;&lt;li&gt;Automatic syntax colouring of (static) Python code, including (as an option) line numbering.&lt;/li&gt;&lt;li&gt;New plotting canvas with simple to use api to draw mathematical functions &lt;/li&gt;&lt;li&gt;Drawing &amp; plotting canvas can be set to arbitrary size.&lt;/li&gt;&lt;li&gt;Multiple canvas can now appear on same page.&lt;/li&gt;&lt;li&gt;New addition and simplification to the sound api.&lt;/li&gt;&lt;li&gt;Simplification of error message (tracebacks) more suitable for beginners.&lt;/li&gt;&lt;/ul&gt;&lt;ul&gt;&lt;li&gt; New plotting canvas with simple to use api to draw mathematical functions&lt;/li&gt;&lt;li&gt;Drawing &amp;amp; plotting canvas can be set to arbitrary size.&lt;/li&gt;&lt;li&gt;Multiple canvas can now appear on same page.&lt;/li&gt;&lt;li&gt;New addition and simplification to the sound api.&lt;/li&gt;&lt;li&gt;Simplification of error message (tracebacks) more suitable for beginners.&lt;/li&gt;&lt;/ul&gt;3. New visual design:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Integrated menu which can be styled through custom css&lt;/li&gt;&lt;li&gt;Three sample css styles now included (selectable via the browser menu)&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;4. New content:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;First draft sound tutorial exploring concepts of frequency, beats, harmonics (Fourier series), etc.&lt;/li&gt;&lt;li&gt;New addition to the basic "Crunchy user" tutorial&lt;/li&gt;&lt;li&gt;Additions to all reference documentation (sound &amp;amp; graphics api, vlam options, etc.)&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-115621225155932289?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/115621225155932289/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=115621225155932289' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/115621225155932289'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/115621225155932289'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2006/08/securing-crunchy.html' title='Securing Crunchy'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-115466648125520971</id><published>2006-08-04T01:23:00.000-03:00</published><updated>2006-11-13T18:10:09.807-04:00</updated><title type='text'>Exciting Crunchy News</title><content type='html'>While I knew all along it could be done, I've had a lot of fun during the last two days playing with the newest, and possibly most exciting feature of "The application formerly known as Crunchy Frog", or simply "Crunchy".  It is now possible, from within a Firefox windows, to click a button and launch an external Python script.  (I should mention that the bulk of this latest feature was implemented by Johannes Woolard - I just had to make a few minor changes to get it working on Windows.)&lt;br /&gt;&lt;br /&gt;I'll provide more details later, but here's the short story.  You write an html document in which you insert the Python script you want to execute inside a &amp;lt;pre&amp;gt; element with some minor markup (&amp;lt;pre title="external copycode"&amp;gt;); actually, you can have as many scripts you want on any given page.  Crunchy processes the page, displays the Python scripts in colour (!) and makes a copy of each available for further editing inside a &amp;lt;textarea&amp;gt;.  You can then just click on a button and the script is launched.&lt;br /&gt;&lt;br /&gt;This means that Crunchy can really transform *any* Python tutorial into an interactive experience.  Crunchy can load html pages from the web (not only local ones) and perform its magic ;-)&lt;br /&gt;&lt;br /&gt;So far, I've launched Tkinter windows, wxPython apps, Pygame programs, a terminal with a Python interpreter and, the latest, a gnuplot window that ran the gnuplot demo.  You can actually have a number of independent external apps going at the same time, allowing for some interesting experiments (edit the code in the &amp;lt;textarea&amp;gt; and launch the new version).&lt;br /&gt;&lt;br /&gt;Stay tuned for more news soon, and the announcement of the next release.&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-115466648125520971?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/115466648125520971/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=115466648125520971' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/115466648125520971'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/115466648125520971'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2006/08/exciting-crunchy-news.html' title='Exciting Crunchy News'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-115360825553067829</id><published>2006-07-22T18:52:00.000-03:00</published><updated>2006-11-13T18:10:09.550-04:00</updated><title type='text'>Crunchy news</title><content type='html'>It's now been a month since my last blog post and a lot has happened in the meantime.  We (&lt;a href="http://pytute.blogspot.com/"&gt;Johannes &lt;/a&gt;and I) have just released a &lt;a href="http://sourceforge.net/project/showfiles.php?group_id=169458"&gt;new version&lt;/a&gt; of &lt;a href="http://crunchy.sourceforge.net/"&gt;Crunchy Frog&lt;/a&gt;.  We were planning to do a release (version 0.5) a bit earlier but we kept "&lt;span style="font-style: italic;"&gt;fixing this&lt;/span&gt;", "&lt;span style="font-style: italic;"&gt;adding that little bit&lt;/span&gt;", "&lt;span style="font-style: italic;"&gt;changing this&lt;/span&gt;", etc., until I felt it was well beyond what had been planned for the next release.  So, after discussing briefly about it (at Johannes's request), we set up a &lt;a href="http://crunchy.sourceforge.net/faq.html"&gt;roadmap&lt;/a&gt; for Crunchy Frog as a whole (as oppposed to just for Johannes' project) with a date set for the 1.0 release that would coincide with the end of this Summer of Code project for Johannes.&lt;br /&gt;&lt;br /&gt;The new public release (0.6) looks very different from the previous public one (0.4).  Crunchy now has its &lt;a href="http://crunchy.sourceforge.net/"&gt;own site&lt;/a&gt;.  Previously, Johannes had set up an &lt;a href="http://crunchy.python-hosting.com/browser/"&gt;svn repository&lt;/a&gt; which has come extremely handy.  This summer of code project is supposed to be a learning experience from the student (Johannes) under the direction of a more experienced mentor (that would be me).  However, I can say that it has been a huge learning experience for me.  So far, I had only worked on my own project, and only very recently had learned to use an svn repository on my own computer.  Working as part of a team (supposedly as the team leader [I have the final say, if need be]; in reality, it's pretty much a team of equals, each of us having different strengths) has been an experience in itself.&lt;br /&gt;&lt;br /&gt;While we have settled on a roadmap, I can already see some additional features, not mentioned so far, that I would like to implement before the end of the summer.  For those interesed in Crunchy Frog: stay tuned, there could be some very nice new stuff added soon.&lt;br /&gt;&lt;br /&gt;The 0.6 release got some attention outside of "regular channels" and I just found out that there exists another Python project named &lt;a href="http://cf.andialbrecht.de/"&gt;CrunchyFrog&lt;/a&gt; (no space between the two words).  This, I think, is most unfortunate and I am in a bit of a quandry as to what to do about the name.  Note that the developper behind CrunchyFrog, who brought its existence to my attention, not only has not complained about me choosing this name, but has actually set up a link to Crunchy Frog on his own website. [This is something we'll definitely have to reciprocate; Johannes is responsible for the website and is taking a well deserved break after the last minute rush to get everything organised for the 0.6 release.]  Nonetheless, the possibility of changing the name of the project has to be considered ... even though Johannes has designed a nice graphical interface based on a Frog theme.&lt;br /&gt;&lt;br /&gt;With all this attention devoted to Crunchy Frog, Rur-ple's development has been suspended.  However, just a few days ago, &lt;a href="http://michel.weinachter.free.fr/"&gt;Michel Weinachter&lt;/a&gt; sent me&lt;br /&gt;&lt;ul&gt;&lt;li&gt;a "patch" so that the user-chosen language can be remembered from one session to the next;&lt;/li&gt;&lt;li&gt;an exe version, produced by py2exe, so that Windows users don't need to install Python nor wxPython to use Rur-ple;&lt;/li&gt;&lt;li&gt;some simple code to make use of a clipboard within rur-ple; I have not had time to see how to make this work though;&lt;/li&gt;&lt;li&gt;just today, a link to a &lt;a href="http://tabinta.mozdev.org/"&gt;Firefox extension&lt;/a&gt; useful to users of Crunchy Frog.  This extension enables the "normal" use of a tab key within an html &amp;lt;textarea&amp;gt;, which comes in handy when typing Python code.&lt;/li&gt;&lt;/ul&gt;Merci Michel!&lt;br /&gt;&lt;br /&gt;So, all in all, a busy month with lots of &lt;span style="font-weight: bold; font-style: italic;"&gt;long &lt;/span&gt;nights of coding after work.  I have a great job, which does not and never will require me to do any programming [sometimes, much to my chagrin.]  However, as a hobby, programming in Python has really been a fantastic one.  It is my hope that the programs I create will be found useful by others.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-115360825553067829?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/115360825553067829/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=115360825553067829' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/115360825553067829'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/115360825553067829'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2006/07/crunchy-news.html' title='Crunchy news'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-115103126304529256</id><published>2006-06-22T23:48:00.000-03:00</published><updated>2006-11-13T18:10:09.126-04:00</updated><title type='text'>Almost there: version 0.9.9 of RUR-PLE has been released</title><content type='html'>The title of this post pretty  much says it all.  I just need to write a few more lessons, read over once more all the existing lessons, do once more all the suggested exercises ... and, barring any surprises, I will be ready to release version 1.0 of RUR-PLE.  Thus will end an adventure that started a little less than 2 years ago, as I decided to start a new hobby and learned about Python.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-115103126304529256?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/115103126304529256/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=115103126304529256' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/115103126304529256'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/115103126304529256'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2006/06/almost-there-version-099-of-rur-ple.html' title='Almost there: version 0.9.9 of RUR-PLE has been released'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-115066553839451004</id><published>2006-06-18T16:33:00.000-03:00</published><updated>2006-11-13T18:10:08.847-04:00</updated><title type='text'>Chasing an unseen bug</title><content type='html'>A few days ago I was contacted by M.H., a professor in the U.S. who teaches Introduction to Computer Programming every year and was thinking of using RUR-PLE.  [&lt;span style="font-style: italic;"&gt;Great, &lt;/span&gt;I thought&lt;span style="font-style: italic;"&gt;, more user feedback to be expected.&lt;/span&gt;]  Unfortunately, M.H. had tried the latest version and found that the code highlighting feature [described in &lt;a href="http://aroberge.blogspot.com/2006/05/rur-ple-getting-closer-to-10.html"&gt;this post&lt;/a&gt;] did not work.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;How could I possibly find a bug that I can not reproduce?&lt;/span&gt; I thought...  Thus began an exchange of emails that led me to find a way to reproduce the bug and, eventually, to solve it.  The final solution was somewhat typical of my experience with Python: &lt;span style="font-weight: bold; color: rgb(102, 0, 0);"&gt;try the obvious&lt;/span&gt;!&lt;br /&gt;&lt;br /&gt;These last three words are probably the best words of advice I could give to anyone who writes computer programs using Python.  If you are reading this blog as a potential source of useful tips [&lt;span style="font-size:78%;"&gt;you fool!&lt;/span&gt;], you should probably stop now as nothing else I will write is likely to come even close to being as useful to you.&lt;br /&gt;&lt;br /&gt;However, if you want to get a good laugh at my expense, you are invited to continue reading.&lt;br /&gt;&lt;br /&gt;I first started by asking M.H. what everyone always asks me when I claim to find a bug or when I don't understand the behaviour of some module and post some question on various lists.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;What OS and what version of wxPython are you using?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I expected that M.H. was using Mac OS or Linux,  in which case I was getting ready to diplomatically tell him that he was likely out of luck as I'm [&lt;span style="font-size:78%;"&gt;shame on me&lt;/span&gt;], a Windows user.  However, M.H. told me that he was using Win XP (same here), Python 2.4.3 (I'm using 2.4.2) and wxPython 2.6 (same here).&lt;br /&gt;&lt;br /&gt;Ok. I was more stumped then ever.  This was bugging me...  These days, I'm working on Crunchy Frog which will, eventually, include and supersede RUR-PLE.   As it is, I have a lot more ideas than time at my disposal to work on Crunchy Frog, and this "distraction" was not welcome.  However, I could not ignore it.&lt;br /&gt;&lt;br /&gt;Since I couldn't reproduce the bug, I asked M.H. if he could do me a favour and insert a &lt;span style="font-weight: bold; color: rgb(51, 51, 255);"&gt;print &lt;/span&gt;statement in the method where the line number was set for the purpose of highlighting.  As I wrote the email, I had a "clever" thought.    &lt;span style="font-style: italic;"&gt;M.H. is in the U.S.  He's probably using the ansi version of wxPython whereas I'm using the unicode version.  I bet you something's wrong with that version...&lt;/span&gt;  Sure enough, my first hunch was right: M.H. was using the ansi version.  And the &lt;span style="font-weight: bold; color: rgb(51, 51, 255);"&gt;print &lt;/span&gt;statement was outputting the same result on his computer as it was on mine.  So, if one hunch was right... I asked M.H. to install the wxPython unicode version, which he kindly agreed to.&lt;br /&gt;&lt;br /&gt;Same result...  &lt;span style="font-weight: bold; font-style: italic;"&gt;Of course&lt;/span&gt;, you are no doubt thinking.  However, you are not trying to quash a bug you can't even reproduce.  [&lt;span style="font-size:78%;"&gt;That's my excuse, anyway.&lt;/span&gt;]&lt;br /&gt;&lt;br /&gt;At that time I was visiting my partner who, unfortunately, lives 300 km (200 mi) from where I live.  Fortunately, she is extremely patient and puts up with me when I am distracted by a programming problem.  I decided to install wxPython and the latest version of RUR-PLE on her computer to investigate some more.&lt;br /&gt;&lt;br /&gt;Ooops...  wxPython is now at version 2.6.3.2.  I am sure the version installed on my computer is 2.6.1.0.  I read over the "changes" document to see if I can find anything that I can use.  There does not appear to be anything relevant.   I install the new version ... sure enough, I can "see" the bug (or, should I say, I can't see anything as no highlighting is taking place).&lt;br /&gt;&lt;br /&gt;I read the "changes" document a second (and third) time, just to be sure.  No luck.&lt;br /&gt;&lt;br /&gt;Time to dig in my code.  [&lt;span style="font-style: italic;"&gt;... Where is this object initialised?... This could have been written so much better...  What was I thinking then (about a year ago)... &lt;/span&gt;]  After staring at the code for a while,  being convinced that the highlighting information is properly set (it was working before, after all) I am convinced it is a wxPython bug that was introduced.  I file a bug report and expect to leave it at that.&lt;br /&gt;&lt;br /&gt;However, my conscience is nagging me.  I decide to look at the code again, staring at the method where the highlighting &lt;span style="font-weight: bold;"&gt;&lt;span style="font-style: italic;"&gt;updates&lt;/span&gt;&lt;/span&gt; should take place.  Here it is, with a few added notes.&lt;br /&gt;&lt;pre&gt;&lt;span style="color: rgb(0, 0, 153); font-weight: bold;"&gt;def &lt;/span&gt;update_refresh(self, robot, name):&lt;br /&gt;  &lt;span style="font-weight: bold; color: rgb(0, 0, 153);"&gt;if &lt;/span&gt;'robot' &lt;span style="font-weight: bold; color: rgb(0, 0, 153);"&gt;in &lt;/span&gt;self.robot_dict:  &lt;span style="color: rgb(0, 102, 0);"&gt;# (1)&lt;/span&gt;&lt;br /&gt;      arg = self.parent.status_bar.beeper_field, self.robot_dict['robot']._beeper_bag&lt;br /&gt;      event_manager.SendCustomEvent(self.parent, arg)&lt;br /&gt;  time.sleep(robot.delay)  &lt;span style="color: rgb(0, 102, 0);"&gt;# (2)&lt;/span&gt;&lt;br /&gt;  wx.Yield()   &lt;span style="color: rgb(0, 102, 0);"&gt;# (3)&lt;/span&gt;&lt;br /&gt;  self.world.DoDrawing()  &lt;span style="color: rgb(0, 102, 0);"&gt;#(4)&lt;/span&gt;&lt;br /&gt;  self.WorldDisplay.drawImage()  &lt;span style="color: rgb(0, 102, 0);"&gt;#(5)&lt;/span&gt;&lt;br /&gt;  if name:&lt;br /&gt;      self.WorldDisplay.scrollWorld(name)  &lt;span style="color: rgb(0, 102, 0);"&gt;# (6)&lt;/span&gt;&lt;br /&gt;  self.WorldDisplay.Refresh()  &lt;span style="color: rgb(0, 102, 0);"&gt;# (7)&lt;/span&gt;&lt;br /&gt;  wx.Yield()&lt;br /&gt;&lt;/pre&gt;&lt;ol&gt;&lt;li&gt;The information in the status bar (number of beepers carried by robot, etc.) is updated.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;The animation is paused at each step.&lt;/li&gt;&lt;li&gt;Making sure other controls of the application are responsive so that the program can be paused or stopped altogether by pressing the appropriate button, etc.&lt;/li&gt;&lt;li&gt;Update the (in-memory) image based on the latest robot action.&lt;/li&gt;&lt;li&gt;Update the screen image.&lt;/li&gt;&lt;li&gt;Prevent the robot from going off the screen.&lt;/li&gt;&lt;li&gt;Refresh the screen [Refresh() is a wxPython method.]&lt;br /&gt;&lt;/li&gt;&lt;/ol&gt;That's it.   Not much there.  I refresh the screen so as to ensure that the robot world, right next to the code "window", gets updated on the screen when a new line of code is executed, and should be highlighted in the code window.&lt;br /&gt;&lt;br /&gt;Can you see the bug?&lt;br /&gt;&lt;br /&gt;I didn't refresh the code window.  It worked before though.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;How do I do this... the robot world is a ScrolledWindow whereas the code window is a StyledTextControl... Where do I find the relevant documentation...&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;That's when I thought of the three important words I mentioned before: &lt;span style="font-weight: bold;"&gt;try the obvious.&lt;/span&gt;  I typed in&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;  self.ProgramEditor.Refresh()&lt;br /&gt;&lt;/pre&gt;and launched RUR-PLE.  Problem solved.  I guess it was just dumb luck that this refresh statement was not required before.  All I have to do now is fix the bug and update the version on sourceforge ... before I go back and rewrite the parts of the code that I didn't find easy to read  just a few days ago.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-115066553839451004?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/115066553839451004/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=115066553839451004' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/115066553839451004'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/115066553839451004'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2006/06/chasing-unseen-bug.html' title='Chasing an unseen bug'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-114954770857962289</id><published>2006-06-05T19:42:00.000-03:00</published><updated>2006-11-13T18:10:08.640-04:00</updated><title type='text'>Praise for Python</title><content type='html'>&lt;span style="color: rgb(0, 0, 0);font-family:tahoma,arial,sans-serif;" &gt;Sometimes, I come accross something which I really wish I could have written.  Usually, I just smile, try to commit it to memory, and move on.  This time, I have to quote what Jim Roskind wrote on &lt;a href="http://www.artima.com/forums/flat.jsp?forum=106&amp;thread=161207&amp;amp;start=30&amp;msRange=15"&gt;Guido's blog&lt;/a&gt;:&lt;br /&gt;&lt;/span&gt;&lt;blockquote&gt;&lt;span style="color: rgb(0, 0, 0);font-family:tahoma,arial,sans-serif;" &gt;&lt;span style="color: rgb(102, 51, 0);"&gt;I was initially a bit put off by the Python indentation approach (I was a grammar guy… and got used to parsing braces and parens, and didn’t like the idea of white space having so much meaning), but I soon came to love it. Eventually I came to apply one of my favorite computer science sayings to Python as an endorsement to their indentation blocking approach: “The fundamental evil in computer science is the replication of code or data.” In almost all languages I had previously worked, the indentation of code had always been a critical PART of program nesting structure (at least for the reader). As I read and wrote more Python, its use of indentation as the ONLY way to specify blocks began to look better and better. All the silly bugs related to indentation errors (misleading the human reader) were gone in Python. The redundant use in other languages of braces AS WELL AS indentation (the former to help the parser, and the latter to help the human reader) was an effective duplication of the author’s intent. &lt;span style="font-weight: bold;"&gt;That duplication in other languages, a fundamental original sin, was missing in Python.&lt;/span&gt; It was cool! ;-)&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;&lt;span style="color: rgb(0, 0, 0);"&gt;Yes, indeed.  Python is cool. :-)&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-114954770857962289?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/114954770857962289/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=114954770857962289' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/114954770857962289'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/114954770857962289'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2006/06/praise-for-python.html' title='Praise for Python'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-114945684059593829</id><published>2006-06-04T18:28:00.000-03:00</published><updated>2006-11-13T18:10:08.402-04:00</updated><title type='text'>RUR-PLE video</title><content type='html'>The kind folks from &lt;a href="http://showmedo.com/videos/video?name=rurple1_andreR"&gt;ShowMeDo&lt;/a&gt;&lt;span style="text-decoration: underline;"&gt;&lt;/span&gt; had contacted me and suggested I make a demonstration video for RUR-PLE.  I'm happy to announce that this has been done, and that the video now appears on the ShowMeDo website.  The link to this demonstration video can be found on the right hand side side-bar for this blog. The sound quality is not all that good, but the video should give a good idea of what RUR-PLE is all about.  &lt;span style="font-size:78%;"&gt;(I will definitely need to get a better microphone for future videos, and possibly for remaking this one.)&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-114945684059593829?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/114945684059593829/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=114945684059593829' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/114945684059593829'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/114945684059593829'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2006/06/rur-ple-video.html' title='RUR-PLE video'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-114856725729145234</id><published>2006-05-25T11:25:00.000-03:00</published><updated>2006-11-13T18:10:08.193-04:00</updated><title type='text'>rur-ple bug fix for Mac and Linux users</title><content type='html'>Brad Miller uncovered a bug (hard coded windows specific path separators) in version 0.9.8.2 of rur-ple.  Version 0.9.8.3 has been uploaded with required changes. Apologies to Linux and Mac users.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9266717-114856725729145234?l=aroberge.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://aroberge.blogspot.com/feeds/114856725729145234/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9266717&amp;postID=114856725729145234' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/114856725729145234'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9266717/posts/default/114856725729145234'/><link rel='alternate' type='text/html' href='http://aroberge.blogspot.com/2006/05/rur-ple-bug-fix-for-mac-and-linux.html' title='rur-ple bug fix for Mac and Linux users'/><author><name>André</name><uri>http://www.blogger.com/profile/08131391818998844540</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9266717.post-114855555217610685</id><published>2006-05-25T07:59:00.000-03:00</published><updated>2006-11-13T18:10:07.986-04:00</updated><title type='text'>Summer of Code 2006</title><content type='html'>For a second year in a row, Google is paying hundreds of students to spend their summer contributing to open source projects.   This is known as &lt;a href="http://code.google.com/soc/"&gt;Summer of Code 2006&lt;/a&gt;.  The Pytho
