Ruby vs Python

Ruby and Python are similar programming languages in that they use much of the same syntax, runtime speed, they both come with large standard libraries to do most common tasks, and are generally suited to solving similar kinds of problems. However, people often strongly prefer one language over the other. Personally, I prefer writing in Ruby. Here I’ll highlight some of the differences between the languages that I think are important.

What are blocks?

One of the great features I like about the Ruby language is “blocks”. Ruby provides convenient syntax for passing a block of code to function/method. The block can take zero or more arguments and do stuff with them. There are two syntaxes for Ruby function/method call with a block; do/end is the same as {}.



func do |arg1| # do stuff with arg1 ... end

func{|arg1| # do stuff with arg1 ... }

The yield statement can be used to call the block when writing a function/method that takes a block.

def func # set-up code yield "some value" # clean-up code end

The ability to easily treat blocks of code as a first class thing in the language gives a huge amount of power. In many of the places Ruby uses blocks, Python introduces new syntax to the language. For example, for loops, with statements and list comprehensions.

Blocks: iterators

To loop over each item in an array/list in Ruby, we use the each method and pass it the block of code we want to be executed for each item. In Python, we instead use a for loop. It is part of the language that utilises the iterator protocol defined on the array/list to loop over each item.

Ruby each call with a block on an Array:

items = [1,2,3,4] items.each do |i| puts i end

Python for loop on a List:

items = [1,2,3,4] for i in items: print(i)

Blocks: “with” statement

We want to run some code that has some standard set-up to be run before our code, or some standard clean-up to be run after our code, such automatically closing a file after we have finished using it. In Ruby we can use blocks; we pass File.open a block of code that we want to run with the file open and it automatically closes the file after the block has finished. In Python, we can do something similar via the with statement.

Ruby File.open block:

File.open('somefile.txt') do |f| puts f.readline end

Python with statement:

with open('somefile.txt') as f: print(f.readline())

Blocks: manipulating arrays/lists

In Ruby we can pass blocks to map to apply an operation on each item and return a new array/list. Here we are adding 1 to each item:

items = [1,2,3,4] newitems = items.map{|i| i + 1 }

Python introduces the syntax for “list comprehensions” within square brackets:

items = [1,2,3,4] newitems = [i + 1 for i in items]

If we want to only select items that are divisible by 2 and then add 1 to we can do…

Ruby:

newitems = items.select{|i| i % 2 == 0 }.map{|i| i + 1 }

Python:

newitems = [i + 1 for i in items if i % 2 == 0]

How about the other way round?

Ruby:

newitems = items.map{|i| i + 1 }.select{|i| i % 2 == 0 }

Python:

newitems = [i for i in [i + 1 for i in items] if i % 2 == 0]

We now have nested list comprehensions in Python, I find it much harder to follow.

Blocks: Sorting by attributes

Say we want to sort an array by a attribute in a Hash/Dict. In Ruby, we can pass a block telling sort_by to use the “width” attribute for each item:

items=[{length: 2, width: 3},{length: 3, width: 4},{length: 4, width: 2}] sorted_items = items.sort_by{|item| item[:width] }

And the result we get is:

[{:length=>4, :width=>2}, {:length=>2, :width=>3}, {:length=>3, :width=>4}]

In Python we can use itemgetter from the operator module to tell sorted to use the “width” attribute:

items=[{'length':2,'width':3},{'length':3,'width':4},{'length':4,'width':2}] import operator sorted_items = sorted(items, key=operator.itemgetter('width'))

The Python example doesn’t exactly seem obvious to me and a more generic way to sort by any expression in Python is to use the lambda syntax. It’s a bit like a block in Ruby:

sorted_items = sorted(items, key=lambda item: item['width'])

Built in functions

Python has lots of functions built into the language that are used to do common operations, such as return the length of an array/list, or to reverse its order. This means that as well as knowing the name of the function to do this, we also need to know if we need to invoke it via len(list) or list.len() . I also think this is confusing for new people learning Python as to why, for example, split is invoked differently from len , plus the documentation for methods/function may be in either the “Built-in Functions” section, or the “Lists” section.

In Ruby, we start reading this from the left with the String object sentence , on which we call split to split on whitespace and return an Array object, on which we call length to return the number of items in the Array.

sentence = 'a short sentence' sentence.split.length

In Python we start on the middle with the String object sentance , on which we call split to split on whitespace and return a List object, which we then pass as an argument to len which returns the number of items in the List.

sentence = 'a short sentence' len(sentence.split())

The Ruby approach typically combines more readably from left to right; for example, if we split the sentence string, reverse it, capitalise the first letter and join it back together we’d do something like…

Ruby:

sentence.split.reverse.map{|word| word.capitalize }.join(' ')

Python:

' '.join([word.capitalize() for word in reversed(sentence.split())])

It takes me a while to work out where to start reading the Python line. I certainly don’t want to think about doing anything with ' ' to start with, as I’ll have forgotten about it when I’ve worked out what all the rest is doing.

() to call methods/functions

The argument for keeping () is to know when it’s a function/method call or when it’s just referencing a variable. In Ruby this situation doesn’t occur because it’s always a method call, so we don’t need to think about it. If we want the member variables of a class to be public, that’s easy: we can use attr_accessor to define “boiler plate” getter and setter methods.

Non Issues

Often one the most debated, but I think largely irrelevant issues, is the significance of whitespace. Python has a colon and syntactically significant whitespace, and Ruby has lines with end on. I think pattern recognition is the key here. If we’re used to seeing end closing off sections of code, it looks initially looks strange if it’s omitted, but most people could easily adapt to recognise either pattern with ease. I think the Ruby programmers’ willingness to use CoffeeScript to generate JavaScript, which does have syntactically significant whitespace, shows this is not really an issue.

Ruby:

def inc(n) n + 1 end

Python:

def inc(n): return n + 1

Same for @ vs self. when referencing class member variables. The Ruby code uses less characters so probably has the advantage.

Ruby:

class MyClass def initialize @n = 0 end end

Python:

class MyClass: def __init__(self): self.n = 0

Closing Remarks

Ruby vs Python is an ongoing debate. Lots of people prefer Ruby and lots of people prefer Python. There are many other differences between the languages, but I think the things covered here are the main things I see people using in these languages, and things that I think new scripters often come across.

One of the great things about a programming language based around blocks, is that once you understand blocks, you can stop worrying about the language itself and start focussing on how to solve your problems. In Python, if you want to go a stage further and write code that allows the language features discussed above to be used on you own objects (instead of using them from libraries), you also need to know the “initialiser protocol”, “generator” syntax, with statement “context managers”, etc.

However, when choosing which language to use, it often better to use the language that the community you want to work with is using. For example, if you want to do scientific computation you’re probably more likely to find a community and some tried and tested libraries in Python. If you want to connect to the latest NoSQL database, you’re probably more likely to find a community and some tried and tested libraries in Ruby. The ability to easily work with existing libraries, or a community, is typically much more important than syntax and semantics of two similar languages like these.

Update: This post has made it onto Reddit: Reddit Python comments, Reddit Programming comments

Update: Following on from the many comments about Python having one way to do things, I’ve written an article on why I didn’t think that was worth including. senktec.com/2013/09/one-way-to-do-it