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: ,

1 Comments:

OpenID kousu said...

The only reason you have to make UserInteger is because int is written as a C extension. With normal classes you can do this:

>>> def s(i):
... print "sexytime"
...
>>> int.__str__=s
Traceback (most recent call last):
File "", line 1, in
TypeError: can't set attributes of built-in/extension type 'int'
>>> class C:
... def __init__(self):
... self.a=1
... def f(self):
... print self.a
...
>>> c=C()
>>> c.f()
1
>>> def g(self):
... self.a+=1
... print self.a
...
>>> c.f()
1
>>> C.f=g
>>> c.f()
2
>>> c.f() #notice how we didn't even need to reinstantiate c!
3
>>> c.f()
4
>>>

just fine. And the python people know that the "Class/Type distinction" is a wart and are working at removing it (not sure if py3k does this, but I'd expect so..).
February 11, 2010 at 5:18 PM

 

Post a Comment

Links to this post:

Create a Link

<< Home