After exploring Ruby briefly in a programming languages class last fall, I’ve finally gotten back around to playing with the language more. Most of my recent experience is in Python, so I’ve had a lot of fun comparing the two languages and their idioms. This list is a personal reference comparing how the same tasks can be solved in both languages.
In the rest of the post, Ruby snippets are on the left and Python snippets on the right. In the snippets, lines of code that correspond between the two languages are aligned, when possible. These examples were tested with Ruby 2.0.0 and Python 2.7.3.
# Ruby snippets on the left
puts "Hello, World!"
# Python snippets on the right
print "Hello, World!"
How to Explore
To run a REPL from the command line:
irb
python
I always like to see what I can do with various objects:
"foo".methods.sort
String.instance_methods.sort
("foo".methods - Object.instance_methods).sort
dir("foo")
To load a file (of function/class definitions, for example) to play with in the REPL:
irb -I . -r file.rb
python -i file.py
Printing and String Interpolation
Printing values is useful for confirming expectations and debugging. Ruby’s .inspect is similar to Python’s repr() in that they both return the string form of the object they are called with. Ruby’s “p” function calls .inspect on its arguments.
a = "test"
puts a
# test
puts a.inspect
# "test"
p a # equivalent to above
# "test"
a = "test"
print a
# test
print repr(a)
# 'test'
String interpolation/formatting is used to put expression or variable values into a string:
puts "value of a: %s" % a
# value of a: test
puts "value of a: #{a}"
# value of a: test
print "value of a: %s" % a
# value of a: test
print "value of a: {0}".format(a)
# value of a: test
Nothing and Truthiness
Testing nothing (represented by nil in Ruby and None in Python):
my_var.nil?
my_var is None
True/false values are lowercase in Ruby and capitalized in Python. The following snippets contain the values that evaluate as false in each language:
false
nil
False
None
0
0.0
""
[]
Boolean Expressions
The potential gotcha in this category is that Ruby’s and and or operators have very low precedence and thus are generally reserved for control flow. For boolean expressions in Ruby, use && and |
. |
!false # true
# these short-circuit:
false && true # false
true || false # true
not False # True
# these short-circuit:
False and True # False
True or False # True
Arrays/Lists
An ordered, integer-indexed collection is called an Array
in Ruby and a list
in Python.
Instantiation
To make arrays/lists:
a = []
b = [1,2,3]
c = Array.new
d = Array[1,2,3]
e = (1..5).to_a
a = []
b = [1,2,3]
c = list()
d = list([1,2,3])
e = list(range(5))
Operations
Some simple operations available on arrays:
a = [1,2]; b = [3,3]
a.include?(1) # true
a + b # [1,2,3,3]
# makes shallow copies:
[a] * 2 # [[1,2],[1,2]]
a[0] # 1
a.first # 1
a[-1] # 2
a.last # 2
a.size # 2
a.length # 2
a.count # 2
b.index(3) # 0
b.count(3) # 2
a = [1,2]; b = [3,3]
1 in a # True
a + b # [1,2,3,3]
# makes shallow copies:
[a] * 2 # [[1,2],[1,2]]
a[0] # 1
a[-1] # 2
len(a) # 2
b.index(3) # 0
b.count(3) # 2
Slicing
Slicing in Ruby is typically done with Range objects, which have inclusive and exclusive forms relative to the end-value as follows: 0..2 contains [0,1,2] and 0…2 contains [0,1]. Basic slicing:
vals = [a,b,c,d]
vals[1..2] # [b,c]
vals[1...3] # [b,c]
vals[-3..-2] # [b,c]
vals[-3...-1] # [b,c]
# alternate form:
# array[start_index, length]
vals[1,2] # [b,c]
vals[-3,2] # [b,c]
vals = [a,b,c,d]
vals[1:3] # [b,c]
vals[-3:-1] # [b,c]
Slicing to/from the end of list:
vals = [a,b,c,d]
vals[2...vals.size] # [c,d]
vals[2,vals.size] # [c,d]
vals.last(vals.size - 2) # [c,d]
vals[0...2] # [a,b]
vals[0,2] # [a,b]
vals.first(2) # [a,b]
vals = [a,b,c,d]
vals[2:] # [c,d]
vals[:2] # [a,b]
Note: For list comprehensions and iterating, see Blocks.
Hashes/Dicts
A mapping from keys to values is called a Hash
in Ruby and a dict
` in Python.
Instantiation
To make hashes/dicts:
a = {}
b = {'x'=>1, 2=>2, Fixnum=>3}
c = Hash.new
d = Hash[[['x',1],[2,2]]]
e = Hash.new { |hash,key| hash[key] = [] }
# Blocks! Getting ahead of myself...
a = {}
b = {'x':1, 2:2, int:3}
c = dict()
d = dict([['x',1],[2,2]])
e = defaultdict(list) # from collections module
Operations
Some simple operations available on hashes:
a = {'x'=>2}
a['y'] = 5
a.has_key?('y') # true
a.key?('y') # true
a.delete('y') # 5
!a.include?('y') # true
a.keys # ['x']
a.values # [2]
a.to_a # [['x',2]]
a = {'x':2}
a['y'] = 5
'y' in a # True
del a['y']
'y' not in a # True
a.keys() # ['x']
a.values() # [2]
a.items() # [('x',2)]
Symbols
A unique piece of Ruby that you quickly run into is symbols. Symbols are kind of like immutable strings indicated by a prepended colon (e.g. :vehicle) that have performance advantages over regular, mutable strings in Ruby. Symbols are typically used for naming things like hash keys and for referencing variables, method names, etc.
Some symbol operations:
:foo.to_s # "foo"
"foo".intern # :foo
"foo".to_sym # :foo
:foo == :foo # true
:foo.object_id == :foo.object_id # true
Control Flow
Everything is pretty much the same here except Ruby gives more options (an obvious trend at this point):
If-block:
if true
puts "Gets here"
elsif false
puts "Doesn't get here"
else
puts "Definitely not getting here"
end
if True:
print "Gets here"
elif False:
print "Doesn't get here"
else:
print "Definitely not getting here"
Inline if:
if answer then "right" else "wrong" end
(answer) ? "right" : "wrong"
puts "You're right!" if answer
puts "You're right!" unless !answer
"right" if answer else "wrong"
print "You're right!" if answer
For-loop (in Ruby, for
is just sugar for calling .each
on the given Enumerable):
for i in 0..2
puts i
end
(0..2).each { |i| puts i }
# Blocks! Coming soon now...
for i in range(3):
print i
While-loop:
while true
# do stuff
end
until false
# do stuff
end
while True:
# do stuff
There’s a lot more that could be said here, about break/next, case, and/or, redo and retry, exception-handling, and more.
Methods/Functions
A named chunk of code is a method in Ruby and a function in Python:
def myfunc
puts "Hello!"
end
def myfunc():
print "Hello!"
The return keyword is optional in Ruby; if it’s omitted then the value of the final expression in the method is returned:
def myfunc
1 + 1
end
myfunc # 2
def myfunc2
return "foo"
"nope"
end
myfunc # "foo"
def myfunc():
1 + 1
myfunc # None
def myfunc2():
return "foo"
myfunc # "foo"
Ruby methods and Python functions can accommodate optional parameters:
def myfunc(x, y=2)
x + y
end
myfunc(2) # 4
myfunc(2,3) # 5
def myfunc(x, y=2):
return x + y
myfunc(2) # 4
myfunc(2,3) # 5
Both languages can collect extra arguments into an array:
def myfunc(x, y=2, *args)
args
end
myfunc(1, 2, 3, 4, 5) # [3,4,5]
def myfunc(x, y=2, *args):
return args
myfunc(1, 2, 3, 4, 5) # (3,4,5)
Ruby allows key-value pairs as arguments and both languages can collect extra keyword arguments:
def myfunc(req, optional=2, *args,
foo: 'bar', **options)
p args
p foo
p options
end
myfunc(1, 2, 3, hey:'there', hi:3)
# [3]
# "bar"
# {:hey=>"there", :hi=>3}
def myfunc(req, optional=2, *args, **kwargs):
print args
print kwargs
myfunc(1,2,3, hey='there', hi=3)
# (3,)
# {'hi': 3, 'hey': 'there'}
Only slightly related to methods/functions but worth mentioning: the * (splat) and ** (double splat) operators in both languages can be used to unpack arrays and dictionaries (e.g. into method calls or a list of variables).
Blocks, Procs, Lambdas
At last! Blocks are my favorite part of Ruby so far, because they are the closest thing to Python’s comprehensions (my favorite part of Python) and generators and the best way to accomplish tasks such as map/reduce/filter.
Robert Sosinski covers blocks/procs/lambdas quite thoroughly, so I’ll keep my treatment to a summary and examples.
Summary
Blocks are anonymous functions that are (usually) implicitly passed to methods. Blocks are indicated by braces (common practice for one-line blocks) or do..end (for multi-line blocks). Arguments yielded to the block are inside pipes ( | arg1, arg2,… | ). |
# don't do foreach like this normally!
def foreach(arr)
puts "block given? #{block_given?}"
for i in 0...arr.size
yield arr[i] # passes arr[i] to the block
end
end
foreach([1,2,3]) { |x| puts x**2 }
# block given? true
# 1
# 4
# 9
foreach([1,2,3]) do |x|
# do..end syntax for multi-line blocks
puts x**2
end
# block given? true
# 1
# 4
# 9
# definitely don't do this!
def foreach(arr):
for i in range(len(arr)):
yield arr[i]
for x in foreach([1,2,3]):
print x**2
# 1
# 4
# 9
# the above is obviously very unnecessary,
# but it illustrates some of the similarity
# between Ruby blocks and Python generators
The block can be bound explicitly as a method parameter:
def foreach(arr, &block)
puts "block class: #{block.class}"
for i in 0...arr.size
block.call(arr[i]) # passes arr[i] to the block
end
end
foreach([1,2,3]) { |x| puts x }
# block class: Proc
# 1
# 2
# 3
def foreach(arr, myfunc):
for i in range(len(arr)):
myfunc(arr[i])
foreach([1,2,3], lambda x: print x)
# this is the equivalent of the Ruby code but
# won't work because lambdas in Python must
# contain 1 expression -- a SyntaxError is
# thrown when they contain statements like "print"
The previous example also revealed that blocks are instances of Proc, which can be manually created and called:
double = Proc.new { |x| x * 2 }
double.call(3) # 6
double = lambda x: x * 2
double(3) # 6
Procs (and blocks) are flexible about how many arguments they are passed:
exam = Proc.new do |x,y|
puts "#{x.inspect}, #{y.inspect}"
end
exam.call(1,2) # "1, 2"
exam.call(1) # "1, nil"
exam.call(1,2,3) # "1, 2"
Lambdas are pretty similar, but are strict about how many arguments are passed (though parameters can be specified as optional):
exam = lambda do |x,y=3|
puts "#{x.inspect}, #{y.inspect}"
end
exam.call(1,2) # "1, 2"
exam.call() # ArgumentError: wrong number of arguments (0 for 1..2)
exam.call(1) # "1, 3"
exam.call(1,2,3) # ArgumentError: wrong number of arguments (3 for 1..2)
# alternate syntax
exam = ->(x, y=3) do
puts "#{x.inspect}, #{y.inspect}"
end
The other main difference between lambdas and Procs is the return behavior. Lambdas return from themselves, whereas Procs return out of the method in which they are enclosed:
def method1
Proc.new { return "from proc" }.call
return "from method1"
end
def method2
lambda { return "from lambda" }.call
return "from method2"
end
method1 # "from proc"
method2 # "from method2"
An article by Dave Thomas shows how Procs and lambdas can be curried.
Examples
Iterating:
[1,2,3].each { |x| puts x }
# 1
# 2
# 3
[:a,:b,:c].each_with_index do
|letter, index| puts "#{index} #{letter.inspect}"
end
# 0 :a
# 1 :b
# 2 :c
for x in [1,2,3]:
print x
# 1
# 2
# 3
for index, letter in enumerate(['a','b','c']):
print index, letter
# 0 a
# 1 b
# 2 c
Mapping:
a = [1,2,3]
a.map { |x| x**2 } # [1,4,9]
# map returns a new array
# use map! to mutate the original
a.collect { |x| x**2 } # [1,4,9]
# collect is an alias of map
a = [1,2,3]
[x**2 for x in a] # [1,4,9]
map(lambda x: x**2, a) # [1,4,9]
Reducing:
a = [1,2,3]
a.reduce(:+) # 6 (uses :+ method on Fixnum)
a.reduce(10, :+) # 16 (initial value of 10)
a.reduce(10) { |sum,x| sum + x } # 16
# inject is an alias of reduce
a = [1,2,3]
import operator
reduce(operator.add, a) # 6
reduce(operator.add, a, 10) # 16
reduce(lambda sum,x: sum + x, a, 10) # 16
# unfortunately there's no pleasant way to
# reduce with a comprehension
Filtering:
a = [1,2,3]
a.select { |x| x.even? } # [2]
a.find_all { |x| x.odd? } # [1,3]
a = [1,2,3]
[x for x in a if x % 2 == 0] # [2]
filter(lambda x: x % 2 == 1, a) # [1,3]
Say you want to find the sum of the cubes of the odd numbers between 0 and 20:
# using \ to write across multiple lines
(0...20).select { |x| x.odd? } \
.collect { |x| x**3 } \
.inject(0, :+)
# 19900
# using \ to write across multiple lines
import operator
reduce(operator.add, \
[x**3 for x in range(20) \
if x % 2 == 1], 0)
# 19900
Most functions from Python’s itertools (combinations, permutations, group_by, etc.) exist as methods on Enumerable or Array or can be created with blocks and, as appropriate, Enumerable.lazy (but know that lazy is slower!).
Classes
Final topic for this list! Classes are blueprints (usually containing related data and behaviors) from which instances are created.
class Person
attr_reader :name # getter for name
attr_accessor :mood # getter/setter for mood
@@count = 0 # class var
# constructor
def initialize(name, mood=:happy)
@name = name # instance var
@mood = mood # instance var
@@count += 1
end
def self.count # class method
@@count
end
end
Person.count # 0
bob = Person.new('Bob')
bob.mood # :happy
Person.count # 1
amy = Person.new('Amy')
Person.count # 2
class Person:
count = 0 # class var
# constructor
# all instance methods have 'self' as 1st param
def __init__(self, name, mood='happy'):
self.name = name # instance var
self.mood = mood # instance var
Person.count += 1
# class methods have 'cls' as 1st param
@classmethod
def get_count(cls):
return cls.count
Person.count # 0
bob = Person('Bob')
bob.mood # 'happy'
Person.count # 1
amy = Person('Amy')
Person.get_count() # 2
OOP concepts and tools extend far beyond this example, and hopefully I can cover topics such as inheritance, mixins, metaprogramming, modules, namespaces, and more in the future. I’ll wrap up this post here though, as it is already longer than I anticipated! =)
Documentation
Another comparison of Ruby/Python on the Ruby site
Where to find documentation:
- Ruby: http://www.ruby-doc.org/
- Python 2.7: http://docs.python.org/2.7/
Attribution
Thanks to my housemates for editing!
I’ll happily respond to any comments, corrections, or other responses on twitter, email, or GitHub!