Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Learning Python from Ruby; Differences and Similarities

Tags:

python

ruby

People also ask

Is Ruby easier to learn than Python?

TLDR: For Ruby vs. Python, Python is easier to learn than Ruby due to its syntax.

Why is Ruby so similar to Python?

Ruby and Python have many similarities, such as: Both are scripting, server-side, cross-platform, general-purpose programming languages. Both are available under open-source licenses, namely OSI-approved (Ruby) and FSF-approved (Python) licenses.

Is Ruby on Rails similar to Python?

The Language: The Ruby on Rails web framework is built using the Ruby programming language while the Django web framework is built using the Python programming language. This is where many of the differences lay. The two languages are visually similar but are worlds apart in their approaches to solving problems.


Here are some key differences to me:

  1. Ruby has blocks; Python does not.

  2. Python has functions; Ruby does not. In Python, you can take any function or method and pass it to another function. In Ruby, everything is a method, and methods can't be directly passed. Instead, you have to wrap them in Proc's to pass them.

  3. Ruby and Python both support closures, but in different ways. In Python, you can define a function inside another function. The inner function has read access to variables from the outer function, but not write access. In Ruby, you define closures using blocks. The closures have full read and write access to variables from the outer scope.

  4. Python has list comprehensions, which are pretty expressive. For example, if you have a list of numbers, you can write

    [x*x for x in values if x > 15]
    

    to get a new list of the squares of all values greater than 15. In Ruby, you'd have to write the following:

    values.select {|v| v > 15}.map {|v| v * v}
    

    The Ruby code doesn't feel as compact. It's also not as efficient since it first converts the values array into a shorter intermediate array containing the values greater than 15. Then, it takes the intermediate array and generates a final array containing the squares of the intermediates. The intermediate array is then thrown out. So, Ruby ends up with 3 arrays in memory during the computation; Python only needs the input list and the resulting list.

    Python also supplies similar map comprehensions.

  5. Python supports tuples; Ruby doesn't. In Ruby, you have to use arrays to simulate tuples.

  6. Ruby supports switch/case statements; Python does not.

  7. Ruby supports the standard expr ? val1 : val2 ternary operator; Python does not.

  8. Ruby supports only single inheritance. If you need to mimic multiple inheritance, you can define modules and use mix-ins to pull the module methods into classes. Python supports multiple inheritance rather than module mix-ins.

  9. Python supports only single-line lambda functions. Ruby blocks, which are kind of/sort of lambda functions, can be arbitrarily big. Because of this, Ruby code is typically written in a more functional style than Python code. For example, to loop over a list in Ruby, you typically do

    collection.each do |value|
      ...
    end
    

    The block works very much like a function being passed to collection.each. If you were to do the same thing in Python, you'd have to define a named inner function and then pass that to the collection each method (if list supported this method):

    def some_operation(value):
      ...
    
    collection.each(some_operation)
    

    That doesn't flow very nicely. So, typically the following non-functional approach would be used in Python:

    for value in collection:
      ...
    
  10. Using resources in a safe way is quite different between the two languages. Here, the problem is that you want to allocate some resource (open a file, obtain a database cursor, etc), perform some arbitrary operation on it, and then close it in a safe manner even if an exception occurs.

    In Ruby, because blocks are so easy to use (see #9), you would typically code this pattern as a method that takes a block for the arbitrary operation to perform on the resource.

    In Python, passing in a function for the arbitrary action is a little clunkier since you have to write a named, inner function (see #9). Instead, Python uses a with statement for safe resource handling. See How do I correctly clean up a Python object? for more details.


I, like you, looked for inject and other functional methods when learning Python. I was disappointed to find that they weren't all there, or that Python favored an imperative approach. That said, most of the constructs are there if you look. In some cases, a library will make things nicer.

A couple of highlights for me:

  • The functional programming patterns you know from Ruby are available in Python. They just look a little different. For example, there's a map function:

      def f(x):
          return x + 1
    
      map(f, [1, 2, 3]) # => [2, 3, 4]
    

    Similarly, there is a reduce function to fold over lists, etc.

    That said, Python lacks blocks and doesn't have a streamlined syntax for chaining or composing functions. (For a nice way of doing this without blocks, check out Haskell's rich syntax.)

  • For one reason or another, the Python community seems to prefer imperative iteration for things that would, in Ruby, be done without mutation. For example, folds (i.e., inject), are often done with an imperative for loop instead of reduce:

      running_total = 0
      for n in [1, 2, 3]:
          running_total = running_total + n
    

    This isn't just a convention, it's also reinforced by the Python maintainers. For example, the Python 3 release notes explicitly favor for loops over reduce:

    Use functools.reduce() if you really need it; however, 99 percent of the time an explicit for loop is more readable.

  • List comprehensions are a terse way to express complex functional operations (similar to Haskell's list monad). These aren't available in Ruby and may help in some scenarios. For example, a brute-force one-liner to find all the palindromes in a string (assuming you have a function p() that returns true for palindromes) looks like this:

      s = 'string-with-palindromes-like-abbalabba'
      l = len(s)
      [s[x:y] for x in range(l) for y in range(x,l+1) if p(s[x:y])]
    
  • Methods in Python can be treated as context-free functions in many cases, which is something you'll have to get used to from Ruby but can be quite powerful.

In case this helps, I wrote up more thoughts here in 2011: The 'ugliness' of Python. They may need updating in light of today's focus on ML.


My suggestion: Don't try to learn the differences. Learn how to approach the problem in Python. Just like there's a Ruby approach to each problem (that works very well givin the limitations and strengths of the language), there's a Python approach to the problem. they are both different. To get the best out of each language, you really should learn the language itself, and not just the "translation" from one to the other.

Now, with that said, the difference will help you adapt faster and make 1 off modifications to a Python program. And that's fine for a start to get writing. But try to learn from other projects the why behind the architecture and design decisions rather than the how behind the semantics of the language...


I know little Ruby, but here are a few bullet points about the things you mentioned:

  • nil, the value indicating lack of a value, would be None (note that you check for it like x is None or x is not None, not with == - or by coercion to boolean, see next point).
  • None, zero-esque numbers (0, 0.0, 0j (complex number)) and empty collections ([], {}, set(), the empty string "", etc.) are considered falsy, everything else is considered truthy.
  • For side effects, (for-)loop explicitly. For generating a new bunch of stuff without side-effects, use list comprehensions (or their relatives - generator expressions for lazy one-time iterators, dict/set comprehensions for the said collections).

Concerning looping: You have for, which operates on an iterable(! no counting), and while, which does what you would expect. The fromer is far more powerful, thanks to the extensive support for iterators. Not only nearly everything that can be an iterator instead of a list is an iterator (at least in Python 3 - in Python 2, you have both and the default is a list, sadly). The are numerous tools for working with iterators - zip iterates any number of iterables in parallel, enumerate gives you (index, item) (on any iterable, not just on lists), even slicing abritary (possibly large or infinite) iterables! I found that these make many many looping tasks much simpler. Needless to say, they integrate just fine with list comprehensions, generator expressions, etc.