Python and Ruby are the two heavyweights in the general-purpose, high-level, dynamic language category. They’re both awesome, but given the choice, I’ll generally go with Ruby. This is my attempt at explaining why…
Ruby is consistently object-oriented
Pretty much everything we do in Ruby involves calling methods on objects:
1 2 3 4 5 6 7 8 |
|
In Python though, a mixture of functions and methods are typically used:
1 2 3 4 5 6 7 8 |
|
One of the benefits of Ruby’s consistency is that we can always read code left-to-right. This makes functional-style pipelines much easier to read and write:
1
|
|
The equivalent Python code is much harder to make sense of. We have to read some parts left-to-right and other parts inside-out. Yuk!
1
|
|
Blocks == awesome
One of Ruby’s best features is the elegant syntax it has for passing anonymous functions (“blocks”) as arguments to methods. Blocks are like Python’s lambda expressions, only more powerful, more elegant, and more widely used.
Blocks allow us to perform many different tasks using a single uniform syntax. A few examples:
1 2 3 4 5 |
|
In Python, we’d typically use no fewer than five language constructs to perform the same tasks:
1 2 3 4 5 |
|
Another thing to note is that Python’s lambda expressions are, as the name suggests, restricted to containing a single expression. Ruby’s blocks have no such restriction, which can be very convenient at times.
One flexible construct > multiple rigid constructs in my book!
Private parts
Instance variables are private by default in Ruby, which encourages proper encapsulation of implementation details.
1 2 3 4 5 6 7 |
|
Everything defaults to public in Python, and privacy can only be suggested by naming conventions. This means we’re more likely to end up with inappropriate dependencies on implementation details.
1 2 3 4 5 |
|
Ruby’s approach seems the wiser one to me. Encapsulation is a Good Idea™, so why shouldn’t the language support and encourage it?
(By the way, Ruby’s approach doesn’t result in C++/Java-style boilerplate. We can generate default getters/setters by placing attr_accessor :property_name
in the class body, and override them later if need be.)
Readability
OK, I’ll admit this one is ever so slightly subjective. Having said that, compare this:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
To this:
1 2 3 4 5 6 7 8 9 |
|
The Ruby code has fewer parentheses, colons, and underscores, and doesn’t need to specify object
, self
, or return
. Python’s whitespace sensitivity does allow it to omit end
, but this doesn’t make up for the other clutter in my opinion.
Loops and if-statements can also be more readable in Ruby. Compare these Python snippets:
1 2 3 4 5 6 7 8 9 10 |
|
To these Ruby ones:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Ruby supports unless
and until
(logical negations of if
and while
), allows question marks to appear in method names, and allows parentheses to be omitted when calling methods. Neat!
As mentioned in previous sections, consistent object-orientation and elegant block syntax also aid Ruby’s readability.
Naming and documentation
The naming conventions used in Python’s standard library are a bit of a mess (even PEP 8 admits as much). Ruby’s standard library is much more consistent in comparison.
In terms of documentation quality, Ruby also has the edge. Descriptions are clearer and more detailed, and usage examples are almost always given. Consider the documentation for str.capitalize
in Python:
1 2 3 4 5 |
|
Then compare it with Ruby’s String#capitalize
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Unless you were paying close attention, you might have missed the fact that str.capitalize
converts the remainder of the string to lowercase (e.g. “ABC” goes to “Abc”). The Ruby documentation makes this behaviour much more obvious, and gives clarifying examples.
This is just one example of course, but these kinds of differences are not atypical in my experience.
Conclusion
So those are a few of the reasons why I prefer Ruby to Python. Admittedly this was a rather one sided comparison – Ruby has plenty of downsides and Python plenty of upsides that I neglected to mention. Perhaps those will be the topic of a future post!