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