Python vs Ruby

From Exterior Memory
Jump to: navigation, search

A lot has been written comparing the Python versus Ruby languages. For no particular reason I'm just adding a few more lines about it.

This article is unfinished.

The Commonalities

Python and Ruby have much in common. Both are modern languages with all the modern requirements:

  • Namespaces
  • Garbage collection
  • Object Oriented
  • Exception Handling
  • Proper Unicode support (Ruby 1.9 and up; Python 3 and up)

What's more, they both

  • are Interpreted (scripting) languages
  • are Dynamically typed
  • have a large user base
  • support a wide range of platforms (Linux, BSD, Windows, Mac). In fact, it comes standard on most of these platforms.

But talking about commonalities is boring. Let's talk about the differences in syntax, language concepts and available codebase.

Compared to Other Languages

It should be understood that neither Python nor Ruby is an "enterprise" language (like Java), in the sense that it has strict typing or makes sure that all exceptions are caught. Instead, both Python and Ruby are geared towards rapid prototyping.

Many Python and Ruby programmers despise Java for its verbosity. While I do like Python and Ruby, I think the comparison is unfair.

C and C++
are great if you like a fast program, and are prepared to cope with headaches about OS (un)interoperability, and a lot of boring low-level stuff.
Python or Ruby
are great if you like rapid prototyping at the expense of speed and robustness.
Java
is great if you like a robust and portable code, at the expensive of spending a lot of time writing error handling code. Great if you know the specs beforehand, and know that these specs are not going to change. (Good luck with this last one).
Objective C and C#
are great if you enjoy lock-in by a single large vendor.

Please don't claim that Python or Ruby programs are generally bug-free. If you catch all exceptions, or check the typebehaviour of every variable, you probably should have used Java in the first place. The whole point of dynamically typed language is that you operated on unknown objects, and basically hope it works fine. That's great because you can very easily change your data structures without rewriting code. Hence the rapid prototyping. But it's not guaranteed to be bug free.

The Syntax

Variable Names

Ruby (as the name implies) is strongly influenced by Perl. Unfortunately, this means that it also inherited the incomprehensible global variables.

Compare:

Ruby Python
$<
$>
sys.stdin
sys.stdout

While a programmer writing code has to spent roughly the same effort to memorise $< and $> or sys.stdin and sys.stderr, someone who is reading the code has a much easier time understanding Python.

Class Methods

Class methods are more elegantly written in Python than in Ruby.

Python uses decorators for class methods and static methods (class methods can access the class variables, static methods can not)

class Date():
   @classmethod
   def fromtimestamp(cls,timestamp):
      """Return a date by it's POSIX timestamp"""
      ...
   @staticmethod
   def fromdate(year, month, day):
      """Return a date by it's Gregorian year, month and day"""
      ...

Ruby uses the self. keyword to refer to the class object, and place a function in the class rather than in the instance.

class Date
   def self.fromTimestamp
      # Return a date by it's POSIX timestamp
      ...
   end
   def self.fromTimestamp
      # Return a date by it's Gregorian part
      ...
   end
end

Equivalent alternative #1

class Date
   ...
end
def Date.fromTimestamp
   # Return a date by it's POSIX timestamp
   ...
end
def Date.fromTimestamp
   # Return a date by it's Gregorian part
   ...
end

Equivalent alternative #2

class Date
   class << self
      def fromTimestamp
         # Return a date by it's POSIX timestamp
         ...
      end
      def fromTimestamp
         # Return a date by it's Gregorian part
         ...
      end
   end
end

Precedence Ambiguity

Ruby syntax can be ambiguous:

functionA 1, functionB 2, 3

May either mean either

functionA(1, functionB(2), 3)
functionA(1, functionB(2,3))

The good news is that Ruby allows you to add enough parenthesis to make even a Lisp-addict happy.

Namespace Ambiguity

namespace -- may give conflicts in Ruby not in Python:

import xyz as abc

Block Syntax

Any self-respecting article discussing Python's syntax can't get around it: indentation as a way to signify nested blocks.

While it has been much debated, I personally find it sheer brilliant. Nearly all attempts at a decent block style syntaxes struggle to somehow combine the condition and nesting start syntax on a single line. The only ones who succeeded here are Python, Ruby and [PHP's old style if/endif control structure syntax]] (that no-one seems to use anymore).

Ruby:

if true
   return :yeah
else
   return :nay
end

Python:

if True:
   return "Yeah"
else:
   return "Nay"

Both syntaxes are very readable, and avoid the dreaded if condition { block } syntax.

Ruby define a second closure (next to the normal methods and lambdas), which is a block (or Proc). While conceptually cool, the syntax is not so great. Ruby allows both:

sum = 0
array.each do |item|
   sum += item
end
sum = 0
array.each { |item|
   sum += item
}

Regardless if you Pascal (begin/end) or C ({/}) is your big inspiration, none of these syntaxes is very inspired, and allowing two different syntaxes will only give rise to heated debates between the two style fanatics. (Please don't tell me the two are different -- I know there is a difference in precedence, but that's a minor annoyance).

Not that Python is debate free. Care for some tab-versus-spaces discussion, anyone?

Objects

In both languages, everything is an object. But unlike Ruby, Python does have a bunch of global functions.

The advantage of Ruby is that it allows for functional style (pipe-like) programming (example taken from http://stackoverflow.com/questions/1113611/what-does-ruby-have-that-python-doesnt-and-vice-versa]). In Ruby, you can read the methods from left to right:

myList.map(&:description).reject(&:empty?).join("\n")

While the Python equivalent, you have to read from right to left:

"\n".join(filter(len, [f.description() for f in mylist]))

Conclusion

The better readability, and the other features make me conclude that Python's syntax is superior over Ruby

From The Zen of Python:

Readability counts.

The Language

One Way

In the syntax section above, I argued that code should not only be easy to write, but also easy to read. Indeed The Zen of Python not only says Readability counts, but also:

There should be one-- and preferably only one --obvious way to do it.

This does not apply to Ruby. For example, the map and collect methods are just aliases, just like the find_all and select methods.

While Python's intentions are clear, in practice it does not always hold. Imagine you are reading a sequence of bytes from a file, Python gives you the primitives (immutable) bytes and (mutable) bytearray, not to mention a (slower) list. Also, you can either use struct.unpack or array.array to do the conversion. Not really a "one obvious way" anymore.

Objects

In Ruby, everything is an object. Every Ruby programmer has marvelled at the fact that even numbers are objects:

5.nonzero?

Python on the other hand, is often criticised by defining global functions such as len() instead of adding it to as a method to every object (behind the scenes, the len(x) function does call a method, x.__len__()).

To me, the fact that len is a function instead of a method is irrelevant. The fact that None is a useful object in Ruby did appeal to me.

For example, it is possible to write:

nil.to_i

and Ruby returns a 0.

The Python equivalent, int(None) raises a TypeError.

Python's None is also an object, but has only about 20 methods (the same amount as an object), while a Ruby nil object has 55 methods.

Opening objects

The real power of Ruby comes from the addition of methods to modify existing objects in-place.

Consider:

class ::Integer
   def even?
      return (self % 2) == 0
   end
end

2.even?
=> true

How is this useful?

Imagine you are reading some JSON data in Python:

try:
   page = urllib.request.urlopen("http://www.example.com/json/address?id=2")
   address = json.loads(page.readall().decode('utf-8'))
   if not isinstance(address, dict):
       raise TypeError("Invalid JSON at URL")
except (urllib.URLError, ValueError, TypeError) as e:
   raise

Now you can start processing the data:

country = address["country"].upper()

Even while we dealt with a failing URL fetch or bad JSON syntax, this last line can fail because "country" is not a valid key, or because address["country"] is not a String (but e.g. None), not to mention it might be an empty string.

try:
   country = address["country"].upper()
except (TypeError, AttributeError, LookupError):
   country = ""

If you want to set many variables, this quickly becomes a lot of repeated code. Python's equivalent of the ?: tertiary operator can turn this into a one-liner:

country = address["country"].upper() if ("country" in address and address["country"] != None) else ""

This is shorter, but not very efficient and still not very readable.

In Ruby, it is is possible to open an existing (even built-in) class, and add a method to it:

class ::Hash
   def value_or_empty key
      if self.has_key? key
         self[key]
      else
         "no"
      end
   end
end

And the code simply reduces to:

country = (address.value_or_empty "country").upcase

This particular example can even be written without resorting to modifying the Hash class:

country = address["country"].to_s.upcase

This is possible because Ruby return nil for an undefined key in a Hash, and because nil.to_s returns an empty string.

This is not easily possible in Python. In Python, you would need to write a decorator class to accomplish the same:

class defaultvaluedict(collections.UserDict):
   """Dict decorator that returns an empty string for unknown keys"""
   defaultvalue = ""
   # UserDict stores the content of the dict in self.data
   def __getitem__(self, key):
      try:
          return self.data[key]
      except LookupError:
          return self.defaultvalue

address = {"country": "nl", "name": "me"}
address = defaultvaluedict(address)
country = address["country"].upper()

While I admire the Ruby flexibility, I do fear that changing objects in-place may yield name collisions. What if I add a method which name is also used for a popular framework that also uses the same name, like the Rails framework? It is likely that my code will break.

Nothing is True

In Ruby 0 is true:

if 0
   puts "True!"
end

While confusing, I found it surprisingly elegant in conditions. No need to write if x != None but just if x. But I never understand why people are still using 0 and 1 as if they are boolean values (the fact that C is crippled in this respect does not need to be carried to other languages).

Protected Methods

Python provides some syntactic sugar to support class-private names, but for all intends and purposes Python has no protected or private methods or variables. Ruby (and most other programming language) normally support protected methods, as well as private, read-only and read/write variables.

In fact, the two solutions provided by Python that I know of are hardly used:

class MyClass(object) :
   def __init__(self) :
      self.__var = ""

a = MyClass()

While a.__var raises an AttributeError, the variable is not really protected, but only mangled. It can still be accessed (and overwritten) using a._MyClass__var. This kind of syntactic sugar reminds me of security through obscurity.



You never directly access attributes. With Ruby, it’s all method calls. Python: http://tomayko.com/writings/getters-setters-fuxors

variable = property(getter, setter)

Named Parameters

Whereas Python fails in providing protected methods, Ruby fails in not providing names parameters.

In Ruby, the only way to tell the parameters of a function apart is their order. Some programmers have a habit of using a single hash for all named parameters. Since Ruby combines all hash items into a single hash, this works seamlessly for the caller, at the cost of a more ugly function definition.

The following Python snippet:

def func(a, b, c=True, d=None, e=5):
   ...

func(1,2,d=4,c=3)

translates in Ruby 1.9 to:

def func(a, b, params)
   c = params[:c] || true
   d = params[:d] || nil
   ...
end

func(1,2, :d => 4, :c => 3)

# Alternative syntax, because in Ruby a Hash {:d => 4} can be written as {d: 4}:
func(1,2, d: 4, c: 3)

On the positive side, Matz has announced named parameters in Ruby 2.0, but if the change from Ruby 1.9 to 2.0 takes as long as the transition to Python 3, it can take some time before we get rid of the ugly hash-as-named-parameter hacks.

Symbols

Ah, Symbols. The named constant or label of Ruby.

:label
:"a label"

It's hard to explain Symbols if you never used them. I tend to call them "labels". They're a constant, but without the need to define (enumerate) constants:

E.g. consider this C code:

enum compass
{
   north,
   east,
   south,
   west
};

A Python library may use constants:

NORTH = 0
EAST = 1
SOUTH = 2
WEST = 3

but in most cases it just uses strings. Consider:

text = data.decode("utf-8", "strict")

Not so pretty, and somewhat efficient. Every time you define this function, a new string is created in memory with the same value (well, not always, and that's why you should |never write string1 is string2 in Python, but string1 == string2)

Ruby instead uses Symbols, which are more efficient, because every two instantiations point to the same object in memory.

a = :"utf-8"
b = :"utf-8"
a.object_id.equal? b.object_id
=> true

The true advantage is that it makes a conceptual difference between "any random string" and a "string with a specific value" such as "utf-8" or "strict" in the above Python code.

I love Symbols.

Tuples

Python has tuples, Ruby does not.

Frankly, who cares? Both languages have mutable sequences. If you want an immutable sequence in Ruby, just freeze an array:

a = 1,2,3
a.freeze

Blocks

Python contains the concepts of methods and lambda, where a lambda is an unnamed method.

Ruby also contains the concept of Procs and blocks, and you can pass a block as parameter to a method:

def runblock
   if block_given?
      yield
   end
end

puts runblock { 1 + 2 }

The runblock method indeed runs the code, and returns 3.

It is possible to execute a block multiple times, even with parameters:

def threetimes
   if block_given?
      yield 0
      yield 1
      yield 2
   end
end

threetimes { |i| print "#{i} " }
=> 0 1 2

In fact, this is a common construct in Ruby:

sum = 0
array.each do |item|
   sum += item
end

In here, { |item| sum += item } both demonstrates a closure (as it operates on the sum variable outside the scope), as well as a code block which is passed to the each method for multiple execution (once for each item in the array).

Robert Sosinski has written a thorough overview about Ruby Blocks, Procs and Lambdas.

Yield

Yields are a awesome way to create iterators. With Python 3, iterators have become more prominent, and rightfully so. They are a recent addition to my programming repertoire, equally important to exceptions, and nested functions. If you are not familiar with them, I suggest to try the first 10 problems in Project Euler.

Python yield syntax easier to understand;

Ruby yield much more powerful

Symbol Table

Confusing: there is more than one symbol table.

Eg.: irb(main):056:0> p "hello" "hello" => "hello" irb(main):057:0> p = 1 => 1 irb(main):058:0> p p 1 => 1

In here, "p" has two meanings


Return Values

//Invalid in most programming languages. Two of the exceptions are Ruby and Scala
my_variable = if (condition) { "yes" } else { "no" };

?: ternary operator

my_variable = (condition) ? "yes" : "no";

As a side-note, it is perfectly possible

 C:          x = (condition ? "yes" : "no")
 Python 2.4: x = (condition and "yes" or "no")
 Python 2.5: x = ("yes" if condition else "no")

Multiple Inheritance

Ruby: “mixin’s” are used instead of multiple inheritance.

Continuations

Supported by Ruby, not by Python.

Useful for propositional logic.

Remove from Ruby 1.9.1? http://www.ruby-forum.com/topic/86862

Unicode

Python 2 3 3.3 UCS-4 default

Conclusion

The block structure and symbols make this a win for Ruby, despite the missing named parameters.

Continuity support in Ruby may be another feature you may appreciate, especially if you have a background in functional languages.

Libraries

Documentation

Python has easy accessible docstrings, Ruby not

Standard Library

standard library. Python much more mature. Ruby: String.shellescape (1.9.3), very poor logging facility (need log4r)

Package Manger

Ruby gem and http://rubygems.org/

Python pip and http://pypi.python.org/

Integration with Other Languages

Java integration Objective C Dot net (C#)


Conclusion

Implementation

Code Validation

Python Zen

Errors should never pass silently. In the face of ambiguity, refuse the temptation to guess.

strict typing, exceptions can pass

Code is not validated; code is only checked while it is run. An error in a less-often used branch

Threading

Python multitasking support is a joke, despite a thread and a threading modules in the standard library. The reason is a global interpreter lock (GIL), which effectively halt all but one thread to ensure consistent state among all threads.

Ruby has a global VM lock, which is roughly the same as the Python GIL.

The way to use concurrency in Python is to use the subprocess module, even though it has some limitations (mostly caused by the pickle format to communicate between processes)

Not a problem according to BDFL, use subprocess: http://mail.python.org/pipermail/python-3000/2007-May/007414.html

Conclusion

Further Reading

sites: http://yehudakatz.com/2010/05/05/ruby-1-9-encodings-a-primer-and-the-solution-for-rails/ http://www.ruby-lang.org/en/documentation/ruby-from-other-languages/to-ruby-from-python/ http://stackoverflow.com/questions/1113611/what-does-ruby-have-that-python-doesnt-and-vice-versa

The Verdict