Why Ruby is More Readable than Python

Why Ruby is More Readable than Python

Ruby and Python are nearly indistinguishable. If a Python programmer opens a Ruby codebase, he’ll be able to understand most of it without having to do external research. The same is true for Ruby programmers who open Python code bases.

However, it’s unlikely that a Rubyist will be able to go through a Python codebase as easily as a Pythonista would a Ruby codebase.

In this post, I’ll show you why.

Objects

Let’s create a class to represent blog posts.

In Python

class BlogPost:

    def __init__(self, title, body):
        self.title = title
        self.body = body
        self.published = False

    def publish(self):
        self.published = True

Let’s play with it:

post = BlogPost(
    title="How I built my blog using Kubernetes, Lambda, and React.",
    body="Wordpress was an insult to my intelligence and so I ...")

post.publish()

I want to be able to print the title of blog posts. There are two ways to accomplish this.

The first one is by simply printing the attribute:

print(post.title)

The second one is to add a __str__ method to our class:

class BlogPost:

    def __init__(self, title, body):
        self.title = title
        self.body = body
        self.published = False

    def publish(self):
        self.published = True

    def __str__(self):
        return self.title

Now we can print the title more easily:

print(post)

What if you want to change the title of a post?

Easy:

post.title = "How I built build my blog using modern instead of simple tools."

In Ruby

Let’s do the same in Ruby:

class BlogPost

    def initialize(title, body)
        @title = title
        @body = body
        @published = false
    end

    def publish
        @published = true
    end

end

Playing with classes is just as easy:

post = BlogPost.new(title="How I built my blog using Kubernetes, Lambda, and React.",
    body="Wordpress was an insult to my intelligence and so I ...")

post.publish

I want to print the title of a post. Let’s try:

puts post.title

Oh no! undefined method `title'

In Ruby, accessing instance variables isn’t possible. You need a getter:

class BlogPost

    def initialize(title, body)
        @title = title
        @body = body
        @published = false
    end

    def publish
        @published = true
    end

    def title
        @title
    end

end

You can’t set attributes directly either — you need a setter:

class BlogPost

    def initialize(title, body)
        @title = title
        @body = body
        @published = false
    end

    def publish
        @published = true
    end

    def title
        @title
    end

    def title=(new_title)
        @title = new_title
    end

end

Now we can play:

puts post.title
post.title = "How I built build my blog using modern instead of simple tools."

What about the __str__ trick we used in Python?

class BlogPost

    def initialize(title, body)
        @title = title
        @body = body
        @published = false
    end

    def publish
        @published = true
    end

    def title
        @title
    end

    def title=(new_title)
        @title = new_title
    end

    def to_s
        @title
    end

end
puts post

Ruby vs Python Objects

Wait so, it looks like both are readable enough. True, but check this out:

class BlogPost:
    count = 0

    def __init__(self, title, body):
        self.title = title
        self.body = body
        self.published = False
        BlogPost.count += 1

    def publish(self):
        self.published = True

    def __str__(self):
        return self.title

We can access the number of posts with BlogPost.count or post.count.

In Ruby:

class BlogPost
    @@count = 0

    def initialize(title, body)
        @title = title
        @body = body
        @published = false
        @@count += 1
    end

    def publish
        @published = true
    end

    def title
        @title
    end

    def title=(new_title)
        @title = new_title
    end

    def to_s
        @title
    end

    def count
        @@count
    end

end

Now we can access post.count but we can’t access BlogPost.count like in Python. Since this is a class variable, we need to be able to access it from the class itself.

class BlogPost
    @@count = 0

    def initialize(title, body)
        @title = title
        @body = body
        @published = false
        @@count += 1
    end

    def publish
        @published = true
    end

    def title
        @title
    end

    def title=(new_title)
        @title = new_title
    end

    def to_s
        @title
    end

    def count
        @@count
    end

    def self.count
        @@count
    end

end

Now we can do BlogPost.count. But we’d rather not be able to do post.count because it could be confused with a regular instance variable.

class BlogPost
    @@count = 0

    def initialize(title, body)
        @title = title
        @body = body
        @published = false
        @@count += 1
    end

    def publish
        @published = true
    end

    def title
        @title
    end

    def title=(new_title)
        @title = new_title
    end

    def to_s
        @title
    end

    def self.count
        @@count
    end

end

Now we can only access count from the BlogPost class. Can we set the class variable though?

Let’s try:

BlogPost.count = 0

Of course not! We never defined a setter for this variable.

What about in Python?

post.count = 0

It works. We can even do it from the class:

BlogPost.count = 0

Ruby objects are more straighforward?

I don’t know about you but I think that it’s easier to see the difference between class and instance attributes in Ruby.

Setters and getters allow you to clearly specify which attributes are readable and writable. You can protect your class attributes by not implementing a setter. In Python, it’s easy to accidentally write to the count attribute — which can break your program.

By default, both post.count and BlogPost.count returns the value of the attribute but it would be easier to notice that it’s a class attribute if it was only accessible from the class.

What do you think about dunder methods (__str__)? Personally, I find to_s to be clearer. It clearly means that calling this method will return a string representation of this object. In Python, you will never do post.__str__ for example. Instead, you’ll do str(post). The str method will call the __str__ method for you and return the result. Sure, all roads lead to Rome but in terms of straighforwardness, they’re not all equal.

More on dunders

Check this out:

class Matrix:

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(self, to):
        return Matrix(self.x + to.x, self.y + to.y)

    def __str__(self):
        return f"({self.x}, {self.y})"

Let’s play:

a = Matrix(1, 1)
b = Matrix(2, 2)
print(a + b)

You should get (3, 3) back.

The same thing in Ruby:

class Matrix
    attr_reader :x, :y

    def initialize(x, y)
        @x = x
        @y = y
    end

    def +(to)
      return Matrix.new(@x + to.x, @y + to.y)
    end

    def to_s
      return "(#{@x}, #{@y})"
    end
end

Play with it:

a = Matrix.new(1, 1)
b = Matrix.new(2, 2)
puts a + b

As you can see, there’s no need to know about special dunder methods in Ruby. If you want your classes to respond to the + operator, simply implement a + method in your class.

I find this to be more readable than Python’s __add__. Behind the scenes, calling + with your objects will call add that will call your object’s __add__ method.

Neo needs to dodge bullets fast and implementing his matrices in Ruby will provide for more straighforward calculations.

Class inheritance

Let’s implement a view to create objects in the DRYest way possible:

class View:
    ...

class ProcessFormView(View):
    ...

class ContextMixin:
    ...

class SingleObjectMixin:
    ...

class FormMixin:
    ...

class ModelFormMixin:
    ...

class BaseCreateView(ContextMixin,  SingleObjectMixin, FormMixin, ModelFormMixin, ProcessFormView):
    ...

class TemplateResponseMixin:
    ...

class SingleObjectTemplateResponseMixin:
    ...

class CreateView(TemplateResponseMixin, SingleObjectTemplateResponseMixin, BaseCreateView):

    def form_valid(self, form):
        ...

Which one of these classes has the form_valid method that I’m overriding? In other words, if I call super().form_valid(form), which class is super() referring to? To figure that out, Python uses an algorithm called C3 to implement something called Method Resolution Order (MRO).

Unless you have Pycharm installed, it’s almost impossible to figure that manually. As a matter of fact, a whole website was created just for helping people figure out the method resolution order of Django’s class based views. Python has full support for multiple inheritance and developers use it lavishly.

In Ruby, multiple inheritance is impossible:

class View:
    ...
end

class ProcessFormView < View:
    ...
end

module ContextMixin
    ...
end

module SingleObjectMixin
    ...
end

module FormMixin
    ...
end

module ModelFormMixin
    ...
end

class BaseCreateView < ProcessFormView
    include ContextMixin
    include SingleObjectMixin
    include FormMixin
    include ModelFormMixin
end

module TemplateResponseMixin
    ...
end

module SingleObjectTemplateResponseMixin
    ...
end

class CreateView < BaseCreateView
    include TemplateResponseMixin
    include SingleObjectTemplateResponseMixin

    def form_valid(form)
        ...
    end
end

A wise man once said “Favor inheritance over composition”. He was executed for heresy.

As you can see, in Ruby, you’re forced to use composition instead of inheritance when you need to share behavior.

In Python, mixins are simply classes. You can instantiate them and they can hold state. In Ruby, mixins are modules and can’t be instantiated. You won’t have to worry about MRO like in Python because:

  1. You’re inheriting from a single super class (no need for MRO).
  2. The last mixin will override anything with a similar name from the previous one.
  3. No constructors are being called.

In Python, you have to pretend like you’re doing composition when you’re actually just doing multiple inheritance. In Ruby, composition is baked into the language. Since Ruby modules aren’t classes, they’re likely to be simpler as well — a big win for readability.

Conclusion

Look at these instructions:

  1. Take a step to your right.
  2. Then 1 step to your left.
  3. Then left again.

And this one:

  1. Take a step forward.

While both languages are much easier to read than say, PHP or Java, Ruby takes it a step further by allowing you to write code that can be understood at a single glance.