Inspirated

 
 

August 1, 2009

Writing a minimal shell in Python

Filed under: Blog — krkhan @ 1:48 am

Being a Unix geek has its own little benefits. For example, when a friend wants you to code a shell for his Operating Systems assignment, you can extort a lunch out of him for the services. Moreover, if you can somehow get him to convince his teacher for accepting submissions in Python, you can have s** with the code itself.

The bare-essentials shell was required to have the following:

  • Support for internal commands such as cd or ls which would be implemented using system calls.
  • Support for launching external programs with arguments provided to the shell — optionally putting them to background with an ampersand at the end of the arguments.

That is it. No fancy intput/output stream redirection, no jobs, no pipes. Just a simple launcher. A quick doze of reflective programming resulted in a class that checked the given commands against member functions (e.g., Shell.on_cd is queried for “cd path“) and called them to process the input. In case of unrecognized commands, the default handler is called which in turn uses the subprocess module and attempts to launch the command as an external program. Support for escaped or quoted arguments wasn’t required (the assignment recommended tokenizing strings at spaces), but the godsent module … :

import shlex
# ...
args = shlex.split(command)

… allowed me to support proper argument parsing with only half a line of code anyway. Similarly, connecting to the standard input/output of the child program wasn’t in the requirements, but the simple call … :

import subprocess
# ...
proc = subprocess.Popen(args,
	executable = args[0],
	stdin = sys.stdin,
	stdout = sys.stdout,
	stderr = sys.stderr)

… did the trick perfectly; allowing proper executions of commands such as “cat -“.

The complete code is downloadable here. Unfortunately, coding was the easy part. Getting the guy to pay for the extorted lunch wasn’t and as of this writing, I’m still deadlocked on that front.

Tags: , , , , , ,

May 17, 2009

Inbox Stats v1.0 — Because graphs speak louder than numbers

Filed under: Blog — krkhan @ 4:00 am

Inbox Stats v1.0

Changelog:

  • As can be seen from the screenshot above, graphs can be turned on through the options menu. Implementing them resulted in two useful modules:
    • scrolledcanvas: Provides a derived Canvas class which has built-in support for scrolling oversizes images.
    • roundedrectangle: Provides functions for drawing rectangles with rounded corners on a Canvas or Image. Optionally, text can be given which will be prettily centered (and truncated upon requirement) in the drawn shapes.
  • More minor code enhancements and bugfixes.

You can find both the modules and the application itself here. All code is released under the PSF license so feel free to use it any way you want. Oh, and if you still haven’t figured out why anyone would be interested in the SMS stats in the first place, here’s a little quote for you:

“Statistics are like a bikini. What they reveal is suggestive, but what they conceal is vital.”

Tags: , , , , , , , , , , , ,

May 14, 2009

User-defined iterators in Python

Filed under: Blog — krkhan @ 5:19 am

Iterable classes are one of the features which make Python code more readable. Simply put, they let you iterate over a container a la:

1
2
for s in ("Spam", "Eggs"):
	print s

Here, s iterates over the tuple printing the words one by one.:

Spam
Eggs

Now comes the interesting part: How do I make my own classes iterable? The official Python Tutorial gives a working example for how to do it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Reverse:
	"Iterator for looping over a sequence backwards"
	def __init__(self, data):
		self.data = data
		self.index = len(data)
 
	def __iter__(self):
		return self
 
	def next(self):
		if self.index == 0:
			raise StopIteration
		self.index = self.index - 1
		return self.data[self.index]
 
value = Reverse('spam')
for char in value:
		print char

Output:

m
a
p
s

The example appeared perfectly fine to a beginner like me. However, since I’m just kinda twisted in the head, I added a new line in the for loop:

16
17
18
19
value = Reverse('spam')
for char in value:
	if char in value:
		print char

Which resulted in the (quite unexpected) output:

 

That’s it. Nothing. Even though the code should make perfect sense and does work in case of built-in types. For example:

1
2
3
4
tup = ("Spam", "Eggs")
for s in tup:
	if s in tup: 
		print s

So daisy-ly gives:

Spam
Eggs

The culprit in case of tutorial’s example for user-defined iterators? After toying around the code sample a little, here’s what I pinned down:

  • On the nested lines where another iterator is required, the Reverse class is supposed to return instance of an iterator which would define the next() method for returning successive values.
  • Since the Reverse class returns only itself in this scenario, the self.index variable is shared among iterators of the Reverse('spam').
  • As a result, Reverse.next() raises the StopIteration in the nested condition.

Once I understood the underlying problem, some further head-scratching and a can of malted drink resulted in the solutions:

  • Return a copy for the iterative functions instead of the instance itself:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    import copy
     
    class Reverse:
    	"Iterator for looping over a sequence backwards"
    	def __init__(self, data):
    		self.data = data
    		self.index = len(data)
     
    	def __iter__(self):
    		return copy.copy(self)
     
    	def next(self):
    		if self.index == 0:
    			raise StopIteration
    		self.index = self.index - 1
    		return self.data[self.index]
     
    value = Reverse('spam')
    for char in value:
    	if char in value:
    		print char

    Pro: Less strain on the programmer, only a couple of extra lines of code are needed.
    Con: copying the instance can be expensive in case of larger containers.

  • Use another class:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    class Reverse:
    	"Iterator for looping over a sequence backwards"
    	def __init__(self, data):
    		self.data = data
     
    	def __iter__(self):
    		return ReverseIter(self)
     
    class ReverseIter:
    	def __init__(self, inst):
    		self.inst = inst
    		self.index = len(self.inst.data)
     
    	def next(self):
    		if self.index == 0:
    			raise StopIteration
    		self.index = self.index - 1
    		return self.inst.data[self.index]
     
    value = Reverse('spam')
    for char in value:
    	if char in value:
    		print char

    Pro: Since the whole container is not copied, only the index is unique among iterators — less burden on the memory.
    Con: Not everyone likes defining new classes.

Both solutions worked equally well and resulted in the same output (the expected one this time):

m
a
p
s

The choice of either solution is solely dependent on the programmer’s preference. As a side note, after equating Python programming with carnal activities in few of my previous posts, I’m gonna take it to the next step and finally tag this post accordingly.

Tags: , , , , , , , , , ,

May 12, 2009

SMS Inbox statistics for Series 60 mobile phones v0.2

Filed under: Blog — krkhan @ 7:04 pm

Update: New version

Improvements in the new version:

  • Previous version hung up while calculating the statistics. The new version dispatches a thread for the dirty work and keeps the user interface responsive with a “Processing” notification.
  • Contact stats are sorted in descending order by the number of messages per each contact.
  • Code improvements for making it more “Pythonic”.

inboxstats.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# -*- coding: utf-8 -*-
"""Script for printing trivial statistics about inbox, such as:
	Number of texts
	Number of unique contacts who sent the texts
	Number of texts sent by respective contacts
"""
 
__author__ = "Kamran Riaz Khan"
__email__ = "krkhan@inspirated.com"
__version__ = "0.2"
__copyright__ = "Copyright (c) 2009 Kamran Riaz Khan"
__license__ = "Python"
__status__ = "Production"
 
import appuifw, e32, inbox, thread
 
def exit_key_handler():
	"Release app_lock."
	app_lock.signal()
 
def parse_inbox_stats(stats):
	"""Parse the inbox statistics,
	Updates the stats dictionary with:
		sms-count : Number of texts
		sms-contacts: List of tuples with following pairs:
			Name of contact, Number of corresponding
			(ordered according to decreasing number of texts)"""
	curr_inbox = inbox.Inbox()
	messages = curr_inbox.sms_messages()
	contacts = {}
 
	for i in messages:
		address = curr_inbox.address(i)
		if contacts.has_key(address):
			contacts[address] = contacts[address] + 1
		else:
			contacts[address] = 1
 
	contacts = contacts.items()
	contacts.sort(lambda x, y: cmp(x[1], y[1]))
	contacts.reverse()
 
	stats["sms-count"] = len(messages)
	stats["sms-contacts"] = contacts
 
def print_inbox_stats(content, stats):
	"""Print inbox stats in the content Text field,
	Remembers the cursor position of Text before the call
	and points at it again after updating the content."""
	pos = content.get_pos()
 
	statsmap = [
		(u"SMS Count", unicode(stats["sms-count"])),
		(u"Unique Contacts", unicode(len(stats["sms-contacts"]))),
		(u"", u"")
		]
 
	statsmap += [(k, unicode(v)) for k, v in stats["sms-contacts"]]
 
	for i in statsmap:
		content.style = appuifw.STYLE_BOLD
		content.add(i[0] + (i[0] and u": " or u""))
		content.style = 0
		content.add(i[1] + u"n")
 
	content.set_pos(pos)
 
if __name__ == "__main__":
	content = appuifw.Text()
	appuifw.app.title = u'Inbox Stats'
	appuifw.app.body = content
	appuifw.app.exit_key_handler = exit_key_handler
 
	stats = {}
	t = thread.start_new_thread(parse_inbox_stats, (stats,))
 
	content.style = appuifw.STYLE_ITALIC
	content.add(u"Processing text messages...n")
	thread.ao_waittid(t)
	content.add(u"Done!nn")
	content.style = 0
 
	print_inbox_stats(content, stats)
 
	app_lock = e32.Ao_lock()
	app_lock.wait()

Inbox Stats v0.2 Screenshot

Tags: , , , , , , , , , , ,

May 10, 2009

SMS Inbox statistics for Series 60 mobile phones

Filed under: Blog — krkhan @ 8:03 pm

Update: New version

Self-indulgence is what I do best. It usually results in me trying to figure out random statistics about my personal life; e.g., graphs about which hours of day I’m mostly awake on and pie-charts about my bathroom habits. Such stuff doesn’t only make me feel more important than I actually am, but also polishes my fundamental math skills which were lost while trying to calculate average number of viruses a Windows user is hit by on an yearly basis.

Texting is what I do second best. Combine the two of my most productive practices and the need emerges of having a way to produce useless statistics about my cell phone’s inbox. This is where PyS60 comes to the rescue. In my previous post I praised Python’s s** appeal. Here’s the demonstration:

  • Total time spent with Python: Less than a week
  • Total time spent with PyS60: Less than a minute
  • Total time spent with Symbian development: Less than never

And still, even a total n00b like me could easily accomplish what he wanted to, using only the library reference manual and 70 lines of understandable code:

inboxstats.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
"""Script for printing trivial statistics about inbox, such as:
	Number of texts
	Number of unique contacts who sent the texts
	Number of texts sent by respective contacts
"""
 
__author__ = "Kamran Riaz Khan <krkhan@inspirated.com>"
__version__ = "$Revision: 0.1 $"
__date__ = "$Date: 2009/05/10 15:30:00 $"
__copyright__ = "Copyright (c) 2009 Kamran Riaz Khan"
__license__ = "Python"
 
import appuifw
import e32
import inbox
 
def exit_key_handler():
	"Release app_lock."
	app_lock.signal()
 
def inbox_stats():
	"""Parse the inbox statistics,
	Returns the dictionary:
		sms-count : Number of texts
		sms-contacts: Dictionary with the pairs:
			contact-name : Number of texts from contact"""
	cur_inbox = inbox.Inbox()
	messages = cur_inbox.sms_messages()
	contacts = {}
 
	for i in messages:
		address = cur_inbox.address(i)
		if contacts.has_key(address):
			contacts[address] = contacts[address] + 1
		else:
			contacts[address] = 1
 
	return {
		"sms-count" :  len(messages),
		"sms-contacts" :  contacts
		}
 
if __name__ == "__main__":
	content = appuifw.Text()
	appuifw.app.title = u'Inbox Stats'
	appuifw.app.body = content
	appuifw.app.exit_key_handler = exit_key_handler
 
	stats = inbox_stats()
	statsmap = (
		(u"SMS Count", unicode(stats["sms-count"])),
		(u"Unique Contacts", unicode(len(stats["sms-contacts"]))),
		)
 
	for i in statsmap:
		content.style = appuifw.STYLE_BOLD
		content.add(i[0] + u": ")
		content.style = 0
		content.add(i[1] + u"n")
 
	content.add(u"n")
	for k, v in stats["sms-contacts"].iteritems():
		content.style = appuifw.STYLE_BOLD
		content.add(k + u": ")
		content.style = 0
		content.add(unicode(v) + u"n")
 
	app_lock = e32.Ao_lock()
	app_lock.wait()

Which gives me:

Inbox Stats Screenshot

Tags: , , , , , , , , , , ,

May 9, 2009

Python for Series 60: Reinvent the ophidian addiction on mobile phones

Filed under: Blog — krkhan @ 9:31 pm

Remember the good old days when playing Snakes on mobile used to be about the most productive thing you could do in a classroom? Well, those days are back, but this time taking guise of another fun reptilian phenomenon: Python for Series 60. If you need to develop/prototype applications on Series 60 devices while having some real fun, you might find PyS60 to be the best thing Nokia did since 1100.

In past, I have tried demystifying the beast known as Symbian development. Truth be told, I really ended up wishing that I had never attempted to do so in the first place. The whole development process was:

  • Extremely bloated: You need about a supercomputer to crunch out one innocent little SIS file without waiting for eons.
  • Error prone: Put the SDKs in a non-standard path and you’re foobar-ed.
  • Intimidatingly cryptic: For a beginner (and by beginner I mean beginner to Symbian, C++ experience apparently proves to be of no help over here), reading Symbian C++ is more or less like reading Perl. Especially since the humor that has developed over the decades resembling Perl code to line noise doesn’t lose any of its appeal here either.

For example, to create a simple notification which would read “Spam and eggs”, I would need to spend about 8 hours downloading, configuring, compiling, comprehending, troubleshooting the development tools. Further 4 for trying to understand how to accomplish something so simple in Symbian code. Granted, such painful development procedures might be required in some scenarios (e.g., where speed is a factor or where masochistic programmers prevail); in PyS60, producing the notification was as simple as:

Python for S60 on Nokia N72

import appuifw
appuifw.note(u"Spam and eggs", "info")

A mammoth two lines of code which I can easily understand without even referring to a book — I feel so cheap.

Tags: , , , , , ,

May 3, 2009

“All methods in Python are effectively virtual”

Filed under: Blog — krkhan @ 8:07 pm

Dive Into Python really is one of the best programming books I have ever laid my hands on. Short, concise and to-the-point. The somewhat unorthodox approach of presenting an alien-looking program at the start of each chapter and then gradually building towards making it comprehensible is extraordinarily captivating. With that said, here’s an excerpt from the chapter introducing Python’s object orientation framework:

Guido, the original author of Python, explains method overriding this way: “Derived classes may override methods of their base classes. Because methods have no special privileges when calling other methods of the same object, a method of a base class that calls another method defined in the same base class, may in fact end up calling a method of a derived class that overrides it. (For C++ programmers: all methods in Python are effectively virtual.)” If that doesn’t make sense to you (it confuses the hell out of me), feel free to ignore it. I just thought I’d pass it along.

If you were able to comprehend the full meaning of that paragraph in a single go, you’re probably (a) Guido van Rossum himself (b) Don Knuth (c) Pinnochio.

Neither of which happens to be my identity, so it took me a few dozen re-reads to grasp the idea. It brought back memories of an interesting question that I used to ask students while I was working as a teacher’s assistant for the C++ course: “What is a virtual function?” The answer always involved pointers and polymorphism; completely ignoring any impact virtual functions would be having on inheritance in referential/non-pointer scenarios. (Considering that most of the C++ books never attempt to portray the difference either, I didn’t blame the students much.) Confused again? Here’s some more food for thought: Python does not even have pointers, so what do these perpetually virtual functions really entail in its universe? Let’s make everything peachy with a nice example.

Consider a Base class in C++ which defines three functions:

  • hello()
  • hello_non_virtual()
  • hello_virtual()

The first function, i.e., hello() calls the latter two (hello_non_virtual() and hello_virtual()). Now, we inherit a Derived class from the Base, and override the functions:

  • hello_non_virtual()
  • hello_virtual()

Note that the hello() function is not defined in the Derived class. Now, what happens when someone calls Derived::hello()? The answer:

Mechanism of virtual function invocation

Since Derived::hello() does not exist, Base::hello() is called instead. Which, in turn, calls hello_non_virtual() and hello_virtual(). For the non-virtual function call, the Base::hello_non_virtual() function is executed. For the virtual function call, the overridden Derived::hello_virtual() is called instead.

Here’s the test code for C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
 
using namespace std;
 
class Base {
public:
	void hello()
	{
		cout&lt;&lt;"Hello called from Base"&lt;<endl; hello_non_virtual();="" hello_virtual();="" }="" void="" hello_non_virtual()="" {="" cout<<"hello="" called="" from="" non-virtual="" base="" function"<<endl;="" virtual="" hello_virtual()="" };="" class="" derived="" :="" public="" public:="" int="" main()="" d;="" d.hello();="" return="" 0;="" <="" pre="">
 
And its output:
 
 
<blockquote>
Hello called from Base
Hello called from non-virtual Base function
Hello called from virtual Derived function
</blockquote>
 
 
 
Similarly, a Python program to illustrate the statement <em>"all methods in Python are effectively virtual"</em>:
 
 
<pre lang="python" line="1">class Base:
	def hello(self):
		print "Hello called from Base"
 
		self.hello_virtual()
 
	def hello_virtual(self):
		print "Hello called from virtual Base function"
 
class Derived(Base):
	def hello_virtual(self):
		print "Hello called from virtual Derived function"
 
d = Derived()
d.hello()

Output:

Hello called from Base
Hello called from virtual Derived function

I hope this clears up the always-virtual concept for other Python newcomers as well. As far as my experience with the language itself is concerned, Python is s**; simple as that. Mere two days after picking up my first Python book for reading, I have fallen in love with its elegance, simplicity and overall highly addictive nature.

Tags: , , , , , , , , , , , , ,

« Previous Page