Friday, February 23, 2007

» String interpolation in python +

In python, the standard way to do string interpolation is to use the % operator. There are some bells and wistles to it, like named substitutions, but overall it behaves much the same as the C printf function on which it was modeled. On the other hand, in ruby you can use inline interpolation with #{} inside a string, which combines perl's ${} and $() into one. I personally prefer the latter method. As of 2.4, python has a similar interpolation method, using the string.Template module, however it is still not as terse (and imo, clean). So here is a simple function to emulate the ruby way (with both expression and variable interpolation).

import sys, re
def interp(string):
locals = sys._getframe(1).f_locals
globals = sys._getframe(1).f_globals
for item in re.findall(r'#\{([^{]*)\}', string):
string = string.replace('#{%s}' % item,
str(eval(item, globals, locals)))
return string

test1 = 'example'

def tryit():
test2 = 1

# variable interpolation
print interp('This is an #{test1} (and another #{test1}) and an int (#{test2})')

# expression interpolation
print interp('This is an #{test1 + " (and another " + test1 + ")"} and an int (#{test2})')

# standard way
print 'This is a %s and a %s and an int (%d)' % (test1, test1, test2)

# since 2.4
from string import Template
map = sys._getframe(0).f_globals
map.update(sys._getframe(0).f_locals)
print 'This is a %(test1)s and a %(test1)s and an int (%(test2)d)' % map
print Template('This is a $test1 and a $test1 and an int ($test2)').substitute(map)

tryit()

I don't mind using the printf style of interpolation, but I prefer the inline method better personally.

[Edit:] Added a cookbook recipe for this.

Labels: ,

Saturday, September 30, 2006

» Scripting Gedit +

Gedit allows you to write python scripts which interface with its backend (and frontend via pygtk). This is very cool, for reasons obvious to VIM and Emacs users. You can write your own plugins to manipulate the document you are editing in many useful ways.

Well I was using various external tools to run my various scripts for test purposes (the External Tools plugin is itself written in python!). So I'd bind F5 to ruby, F6 to python and F10 to perl. Then, depending on the script I was editing, I'd press the corresponding accel key to run it. But I realized that I could just write a plugin that would run any script according to its bang line. The following is the plugin I came up with (place it in ~/.gnome2/gedit/plugins):

First is the plugin definition file (named script_runner.gedit-plugin):

[Gedit Plugin]
Loader=python
Module=script_runner
IAge=2
Name=Script Runner Plugin
Description=This plugin runs scripts by reading the bang line.
Authors=Jordan Callicoat <MonkeeSage@gmail.com>\nJonathan-Marc Lapointe
Copyright=Copyright © Jordan Callicoat, 2006
Website=http://rightfootin.blogspot.com

Then is the actual python script file (named script_runner.py):

#  Script Runner plugin
# coding: utf-8
#
# Copyright © 2006 Jordan Callicoat
# Copyright © 2008 Jonathan-Marc Lapointe
# Released under the python license.

import re
import os
import gtk
import gedit
from externaltools.functions import capture_menu_action
from externaltools.ElementTree import Element

class RunScriptPlugin(gedit.Plugin):
"""
A simple plugin that runs the current document
in the interpreter specified by the bang line
"""

def run_document(self, action, window):
"""
Just Do It!
"""
# read in the first line of the buffer...
buff = window.get_active_view().get_buffer()
siter = buff.get_iter_at_line_offset(0,0)
eiter = buff.get_iter_at_line_offset(1,0)
data = buff.get_text(siter, eiter)
# now check if it contains a bang line...
if data:
text = re.match(r'^#!(.*)$', data)
if text: # it does...
text = text.group(1)
# extract a label (for use in the output
# panel) from the bang line...
label = os.path.basename(text).split(' ', 1)
if label[0].lower() == 'env':
label = label[1]
else:
label = label[0]
# build an ElementTree Element to feed capture_menu_action...
elem = Element('tool')
elem.command = text
elem.name = label # .title()
elem.input = 'document'
elem.output = 'output-panel'
# now run the script!
capture_menu_action(None, window, elem)

def activate(self, window):
"""
Setup stuff
"""
actions = [
('Run', gtk.STOCK_EXECUTE, 'Run Script', 'F5', 'Run Script', self.run_document),
]
# store per window data in the window object
windowdata = {}
window.set_data('RunScriptPluginWindowDataKey', windowdata)
windowdata['action_group'] = gtk.ActionGroup('GeditRunScriptPluginActions')
windowdata['action_group'].add_actions(actions, window)
manager = window.get_ui_manager()
manager.insert_action_group(windowdata['action_group'], -1)
ui_str = """
<ui>
<menubar name="MenuBar">
<menu name="ToolsMenu" action="Tools">
<placeholder name="ToolsOps_3">
<menuitem name="Run" action="Run"/>
<separator/>
</placeholder>
</menu>
</menubar>
</ui>
"""
windowdata['ui_id'] = manager.add_ui_from_string(ui_str)
window.set_data('RunScriptPluginInfo', windowdata)

def deactivate(self, window):
"""
Teardown stuff
"""
windowdata = window.get_data('RunScriptPluginWindowDataKey')
manager = window.get_ui_manager()
manager.remove_ui(windowdata['ui_id'])
manager.remove_action_group(windowdata['action_group'])

def update_ui(self, window):
"""
UI Callback
"""
view = window.get_active_view()
windowdata = window.get_data('RunScriptPluginWindowDataKey')
windowdata['action_group'].set_sensitive(bool(view)) # and view.get_editable()))

After you add these files, you need to restart Gedit. Then you go to Preferences -> Plugins tab -> check the Script Runner Plugin option, and you're set. Now pressing F5 will automagically run any script with a bang line. Cool stuff! :)

Note: The External Tools plugin needs to be enabled for this script to work! Updated to work with newer gedit versions, thanks Jonathan.

Labels: , ,

Saturday, September 23, 2006

» Conventional wisdom +

The conventions of a given community of programmers are usually time-tested and often they make life much easier on the programmer. For example, the convention of writing self-documenting code rather than using one or two letter variable names is a very helpful guideline.

But as with many other issues, what is generally beneficial and a good practice for most people in most situations gets promoted to the absolute best thing for everyone always. And anyone who doesn't think so is either a bad programmer or is stupid for departing from the conventional wisdom of the community.

It seems to me that this kind of thinking can lead to stiffling creativity and growth in problem solving. You just can't get your head around approaching a problem in a different way than the common way (i.e., "only right way"); you'll say "why do you want to do it like that, you should do it like this!" (I've seen this happen many times; regarding everything from XHTML to JavaScript to python to ruby).

And what is silly is that many of these conventions are simply matters of personal taste: for example, coding style. But yet people are instructed that it is best to ignore their own prefered coding style and to just follow along with the community, because the way the community likes it is The Right Way®™; and should the person not follow the conventions of the community, then they must be trying to "program in a different language". "Haven't you read PEP 8?!" it is asked in awe and reverence of the almighty style guide.

Or, when something really is a bad idea sometimes; people get all fearful and turn that into every time. "What? You use...eval?" it is intoned in frightened, hoarse half-wispers, lest the devil drag you into hell for mentioning the name of his son. Oh, but instance_eval is fine though(!).

I'm not some kind of rebel iconoclast, standing against everything conventional. I like most of our conventions, actually. I just don't like it when we cling to them so tightly we refuse (or literally cannot) look past them to mabye improve them or find something better; or when we take what amounts to subjective preference and make it the absolute "right way" to do things; or when we get so worried we'll do something wrong we make it harder to do something right. Uh, yeah. That's about the long and short of it, heh.

And of course, I never do any of that; you see, I have this convention that says... ;)

Labels: , ,

Tuesday, August 29, 2006

» Of Rocks and Reptiles... +

A blog article was posted by jesusphreak the other day about why he chose python over ruby. Several people have commented on this in the blogosphere and mailing-lists (e.g., here, here and here), and in his comments section. So I might as well add my two bits.

I use both ruby and python, though I've only been using python for about 6 months (been using ruby for like...hmmm...3 years or so). Knowing ruby and chrome-level javascript (i.e., from writing firefox extensions) greatly decreased the python learning curve for me because of the similarities with those languages, and I can pretty much do everything I want to in python with minimal effort after just 6 months (writing fairly complex gui apps in any language in under 6 months bespeaks the power and intuitivness of the language -- 'course previous knowledge of ruby and javascript helped!). In fact, I've just been porting a desktop app I wrote in ruby+gtk over to python, and everything is going smoothly. The reason for porting it is mainly for ease of distribution -- I'm getting near to public release, and all the python extensions I'm using have graphical installers for win32, plus python is installed by default on many flavors of *nix, and the speed boost from psyco is nice too.

But don't worry, fellow rubyists, for admin scripts and personal projects I use ruby, mainly because of its expressiveness. Adding methods to built-in objects, transparent regexp support, code blocks, and so forth; I know that I can get all the same results in python, using different techniques, but some of them just look...ugly, by comparison. And I really dislike top-level methods like len() and str()...yuck! I know I can use a class-wrapper to get around this in python...like adding a str() method to a UserInteger wrapper class for class int, and then initializing all my ints as UserInteger instances:

class UserInteger(int):
def str(self):
return str(self)
## YES! You need these...
## the arithmetic operators cast their
## result as a normal int
def __add__(self, i):
return UserInteger(int(self).__add__(int(i)))
def __sub__(self, i):
return UserInteger(int(self).__sub__(int(i)))
def __mul__(self, i):
return UserInteger(int(self).__mul__(int(i)))
def __div__(self, i):
return UserInteger(int(self).__div__(int(i)))
def __radd__(self, i):
return UserInteger(int(i).__radd__(int(self)))
def __rsub__(self, i):
return UserInteger(int(i).__rsub__(int(self)))
def __rmul__(self, i):
return UserInteger(int(i).__rmul__(int(self)))
def __rdiv__(self, i):
return UserInteger(int(i).__rdiv__(int(self)))

n = 5
i = UserInteger(n)
i = ((i * i) / 3) + 2
i.str() # => '10'

...but that's a lot of trouble just to add a str() method to class int -- especially the part about initializing all int objects as instances of the class wrapper rather than just using a literal, not to mention adding methods to the wrapper to ensure type safety. Compare that to ruby's equivalent:

class Fixnum
def str()
self.to_s
end
end

i = 5
i = ((i * i) / 3) + 2
i.str # => "10"

No need to initialize instances of a subclass before my custom methods are accessible -- I've actually extended the base-class itself, not created a custom subclass.

But minor issues aside, the two are very similar (kissing cousins, I'd say). Consider the following sed-like program (which I aptly named gsub ;) ), first in ruby, then in python:

unless ARGV.length > 2
puts 'usage: gsub "PATTERN" "REPLACEMENT" file [file ...]'
exit
end

pattern = ARGV.shift
replace = ARGV.shift
files = ARGV.dup

files.each { |filename|
File.open(filename, 'rb') { |fh|
data = fh.read
if data =~ /#{pattern}/
fh.reopen(filename, 'wb')
fh.write(data.gsub(/#{pattern}/, replace))
end
}
}

And in python:

import sys, re

if len(sys.argv) < 4:
print 'usage: gsub "PATTERN" "REPLACEMENT" file [file ...]'
sys.exit()

pattern = sys.argv.pop(1)
replace = sys.argv.pop(1)
files = sys.argv[1:]

for filename in files:
fh = open(filename, 'rb')
data = fh.read()
fh.close()
if re.search(pattern, data):
fh = open(filename, 'wb')
fh.write(re.sub(pattern, replace, data))
fh.close()

Anyhow, in my own humble opinion, both ruby and python are great languages (in the words of Rodgers and Hammerstein: "the farmer and the cowman should be friends"), and a person should not limit themselves to using only one or the other of them; or at least they should try using both for some time to see which is better suited to their own needs before they make a final decision. It seems that jp found python more adequate to his needs at the time, so more power to him for using it! No haterism here.

Labels: ,