Eric Radman : a Journal

A Comparison of Ruby and Python

Ruby is an extremely consistent, flexible language. These are some examples of how it compares with Python in my experience.

Include Additional Libraries
# rb: fast and convenient; can include libraries from
# arbitrary location at the start of a file
#!/usr/local/bin/ruby -I/usr/local/sqlite3-ruby/lib
require 'sqlite3'
# py: must hard-code library paths in the program or use a
# wrapper script and append to PYTHONPATH
#!/usr/local/bin/python
import sys
sys.path.append("/usr/local/sqlite3-python/lib")
import sqlite3
Gather Results from a command
# rb: fantastic for scripting
arch = `uname -m`
# py: no recommended way to do this
from subprocess import Popen, PIPE
p = Popen(["uname", "-m"], stdout=PIPE)
arch = p.communicate()[0]
List Object Methods
# rb: simple OO
"string".methods.sort
# py: yet another keyword
dir("string")
Print a Hash Sorted on Keys
# rb: standard blocks and simple iterators
h = {"rabbit" => 2, "cat" => 1, "dog" => 3}
pairs = h.sort_by {|k, v| k}
pairs.each do |k, v|
  puts "#{k} : #{v}"
end
# py: sort() mutates the list
d = {"rabbit": 1, "cat": 2, "dog": 3}
keys = d.keys()
keys.sort()
for k in keys:
    print "%s : %s" % (k, d[k])
Print a Hash Sorted on Values
# rb: standard blocks and simple iterators
h = {"rabbit" => 2, "cat" => 1, "dog" => 3}
pairs = h.sort_by {|k, v| v}
pairs.each do |k, v|
  puts "#{k} : #{v}"
end
# py: sorted() takes a function
d = {"rabbit": 1, "cat": 2, "dog": 3}
pairs = sorted(d.items(), key=lambda (k,v): (v,k))
for k, v in pairs:
    print "%s : %s" % (k, d[k])
Clear Hash Entries
# rb: very predictable
h = {"a" => 100, "b" => 200}
h.delete("a")
# py: is this a statement or an operator?
d = {"a": 100, "b": 200}
del d["a"]
Test for a Value
# rb: 0 != nil, all numbers are values
if 0 then "valid" end
# valid
# py: None != 0, but zero is not a value?
if 0:
    "valid"
# condition fails
Test for Equality on a Regular Expression
# rb: regex is seamless
if "item5" =~ /item\d/ then "true" end
# py: regexp is not integrated
import re
p = re.compile('item\d')
if p.match("item5"):
    "true"
Variable Interpolation
# rb: any variable in current environment
pin = 9876
"you're new pin is #{pin + rnd()}"
# py: must build a dictionary
dict = {'pin':9876 + rnd()}
"you're new pin is %(pin)s" % dict
Literal String
# rb: Use single quotes
puts 'the tab character is \t.'
# py: String prefixed with 'r' or 'R'
print R"the tab character is \t."
Heredoc
# rb: use of shift operator allows input to be terminated
# arbitrarily
def send
    <<MESSAGE
From: Eric Radman <ericshane@eradman.com>
To: Test User <test@eradman.com>
Subject: e-mail test

Test Message
MESSAGE
end
# py: tripple-quotes have to be escaped; text cannot start
# on following line
def send():
   return """From: Eric Radman <ericshane@eradman.com>
To: Test User <test@eradman.com>
Subject: e-mail test

Test Message
"""
Access Private Class Variables
# rb: attr_accessor() shortcut enables get/set or we can
# create our own methods
class Person
  attr_reader :name
  def name=(name)
    @name = name
  end
end

# py: mangled names (prefixed by double-underscores) must
# be used, and get/set is not overloaded
class Person(object):
    def set_name(self, name):
        self.__name = name

    def get_name(self):
        return self.__name
Filter Duplicate Values from a List
# rb: very intuitive
[7, 6, 1, 6].uniq
# py: ug, casting to the rescue?
list(set([7, 6, 1, 6]))
Reverse and Return a List
# rb: as with many methods, us ! to mutate
[1, 2, 3].reverse
# py: must mutate the list before returning it
numbers = [1, 2, 3]
numbers.reverse()
return numbers
Hidden classes
# rb: redefining a constant produces a warning
TestMissileLauncher = Class.new do
  def test_safety_lock
    # ...
  end
end

TestMissileLauncher = Class.new do
  def test_launch_controls
    # ...
  end
end
# warning: already initialized constant TestMissileLauncher
# py: dangerous and common--the first class overwrites the
# second, only the second set of tests run
class TestMissileLauncher(object):
    def test_safety_lock(self):
        # ...

class TestMissileLauncher(object):
    def test_launch_controls(self):
        # ...
List Iteration
# rb: no conversion required; the iterator for one element
# functions correctly
def display(items)
  items.each {|v| puts v }
end
display "red"
# py: a string is converted to a list of characters which
# are printed one line at a time. This should at least
# throw a type error 
def display(items):
    for item in items:
        print item
display("red")
Anonymous Functions
# rb: a proc springs into existence whenever a block is used
seq = [1, 2, 3, 4, 5, 6]
seq.map {|x| if x % 2 == 0 then x else x * -1 end }
# py: lambdas can only be simple expressions and conditionals
seq = [1, 2, 3, 4, 5, 6]
def cond_invert(x):
    if not x % 2: return x
    else: return x * -1
map(cond_invert, seq)
Extending Built-in Objects
# rb: any class or instance can be modified
Time.class_eval do
  class << self
    alias_method :real_now, :now
    def now
        real_now + 60
    end
  end
end

# py: impossible to modify built-in types or classes
Testing for Type Equality
# rb: uniform test
var = [1, 2, 3]
var.instance_of? Array
var.is_a? Array
# py: must access __class__ sometimes
var = [1, 2, 3]
isinstance(var, list)
issubclass(var.__class__, list)
REPL Ease of Use
# rb: copy and paste any code 
=> for i in 0..5
=>  puts i
=> end
=> puts "done"
# py: horizontal and vertical whitespace is required. The
# following works in a source file but fails in the REPL
>>> for i in range(0, 5):
...   print(i)
... print("done")
Test for Value in Array
# rb: use methods to discover object properties. index
# method returns nil if not found
[1, 3, 5].include? 4
# py: special operator that would only appear on built-in
# data types. index() requires try/except to trap
# ValueError
5 in [1, 3, 5]
ARGV[0] for One-liners/td>
# rb: first argument is the name provided
ruby -e 'puts ARGV[0]' myscript # "myscript"
# py: first argument is always '-c' or '-'. Might have
# to test for it and possibly use argv.pop(0) to recover
# proper behavior
python -c 'import sys; print sys.argv[0]' myscript # "-c"

Notes

Tests formed with Ruby >= 1.8.7, and Python >= 2.5.1

References

Ruby One-Liners by Dave Thomas

$ 2011-05-23 07:14:17 -0500 $