Gastón Ramos

Personal blog

← Back to the English articles

Leer este artículo en español

October 20, 2026.

Python for Ruby developers: differences, concepts, and common mistakes

I'm learning Python, and Ruby is the language I know best. So I decided to start by comparing the two. They have a lot in common, but a few differences in syntax and behavior can catch me out. Here are the ones I want to keep handy, with examples to compare them.

1. Indentation and code blocks

One of the first differences is how blocks are delimited.

In Ruby, we use end. In Python, indentation is part of the syntax.

Ruby:

if x > 5
  puts "Greater"
end

Python:

if x > 5:
    print("Greater")

In Python:

  • Blocks start after :.
  • Four spaces are the usual indentation.
  • Indentation is required and determines which statements belong to each block.

2. None, True, and False

Python uses different names for some basic values.

Ruby Python
nil None
true True
false False

To check whether a variable is None, you normally use is:

if x is None:
    print("No value")

Using is None instead of == None checks identity and does not depend on a custom equality implementation.

Falsy values

In Ruby, only nil and false are falsy in a condition.

Python has more falsy values, including:

  • None
  • False
  • 0
  • ""
  • []
  • {}
  • ()

For example:

items = []

if items:
    print("Has items")
else:
    print("It's empty")

This prints "It's empty". In Ruby, an empty array is truthy. This matters when working with collections.

3. Function return values

In Ruby, the value of the last evaluated expression is returned automatically.

def add(a, b)
  a + b
end

Python requires an explicit return:

def add(a, b):
    return a + b

If a Python function finishes without executing a return, it returns None.

def add(a, b):
    a + b

print(add(2, 3))  # None

It's a small difference, but an easy one to miss when moving between languages.

4. Handling exceptions: try and except

Ruby uses begin and rescue to handle exceptions.

begin
  result = 10 / 0
rescue ZeroDivisionError
  puts "Error"
end

Python uses try and except:

try:
    result = 10 / 0
except ZeroDivisionError:
    print("Error")

The idea is much the same, but the syntax changes. In both languages, it's better to catch specific exceptions than every error indiscriminately.

5. Lists and tuples

Python has two data structures that may look like Ruby arrays: lists and tuples.

Lists

Lists are mutable and use square brackets.

numbers = [1, 2, 3]
numbers.append(4)

They are comparable to Ruby arrays.

Tuples

Tuples are immutable sequences.

numbers = (1, 2, 3)

Once a tuple is created, we cannot replace, add, or remove its elements. But there's a detail: if a tuple contains a mutable object, that object can still change.

data = ([1, 2], "example")

data[0].append(3)

print(data)  # ([1, 2, 3], 'example')

The tuple itself is still immutable; the list inside it isn't.

6. Multiple assignment and unpacking

Python lets us assign several variables in one statement.

a, b = 10, 20

We can also unpack values returned by a function:

def coordinates():
    return 10, 20

x, y = coordinates()

Here Python packs the two return values into a tuple, then unpacks it. Ruby has a similar feature:

a, b = 10, 20

7. Dictionaries: Python's equivalent of hashes

Python dictionaries are comparable to Ruby hashes.

Ruby:

person = { name: "Juan", age: 30 }

Python:

person = {"name": "Juan", "age": 30}

One difference is that Python has no direct equivalent of Ruby's Symbol type. Dictionary keys must be hashable; strings, numbers, and tuples whose elements are hashable are common choices.

Accessing missing keys

This difference matters. In Ruby:

person = { name: "Juan" }

person[:age]  # nil

In Python:

person = {"name": "Juan"}

person["age"]  # KeyError

If the key does not exist, Python raises KeyError. For behavior closer to Ruby's Hash#[], use get:

person.get("age")  # None

We can also supply a default value:

person.get("age", 0)  # 0

In Ruby, fetch behaves more like Python's bracket access when a key is missing.

8. Mutable default arguments

This is one of the most surprising Python behaviors for someone coming from Ruby. Consider this function:

def add_item(item, items=[]):
    items.append(item)
    return items

print(add_item(1))  # [1]
print(add_item(2))  # [1, 2]
print(add_item(3))  # [1, 2, 3]

Why? Default arguments are evaluated when the function is defined, not each time it is called. The empty list is created once and reused by later calls that omit the argument.

The usual solution

Use None as the default and create the list inside the function:

def add_item(item, items=None):
    if items is None:
        items = []

    items.append(item)
    return items

Now:

print(add_item(1))  # [1]
print(add_item(2))  # [2]
print(add_item(3))  # [3]

In Ruby, default arguments are evaluated on each call, so this problem does not occur in the same way.

9. References, assignment, and copies

Both Ruby and Python work with references to objects. Assigning an object to another variable does not automatically copy it.

Python:

a = [1, 2]
b = a

b.append(3)

print(a)  # [1, 2, 3]

Ruby:

a = [1, 2]
b = a

b << 3

puts a.inspect  # [1, 2, 3]

In both cases, a and b refer to the same object.

Making a copy

In Python:

b = a.copy()

In Ruby:

b = a.dup

Both operations make shallow copies, so nested objects may still be shared.

Reassignment is different from mutation

a = [1, 2]
b = a

b = [3, 4]

print(a)  # [1, 2]

Here we have not changed the original object. We have simply assigned a new list to b. Ruby works similarly.

10. Common Python exceptions

Both languages have similar exception systems, but names and circumstances can differ.

Python Meaning Ruby comparison
AttributeError Missing attribute or method Similar to NoMethodError
TypeError Incompatible operation or incorrect arguments May correspond to TypeError or ArgumentError
ValueError Invalid value for an operation Similar to some ArgumentError cases
KeyError Missing dictionary key Hash#[] usually returns nil
IndexError Index out of range Array#[] usually returns nil
NameError Undefined name Also exists in Ruby
ZeroDivisionError Division by zero Also exists in Ruby

AttributeError

Raised when we access a missing attribute or method.

x = None
x.upper()

TypeError

Raised when we try an operation that is incompatible with the types involved.

result = "10" + 5

ValueError

Raised when the argument's type is acceptable but its value is not.

number = int("hello")

KeyError

Raised when we access a missing dictionary key with brackets.

data = {"name": "Juan"}
data["age"]

IndexError

Raised when we access a list position that does not exist.

numbers = [1, 2, 3]
numbers[10]

In Ruby, the equivalent operation usually returns nil.

NameError

Raised when we use a name that has not been defined.

print(undefined_variable)

ZeroDivisionError

Raised when we try to divide by zero.

result = 10 / 0

11. Reading tracebacks

When Python encounters an uncaught exception, it prints a traceback. For example:

Traceback (most recent call last):
  File "app.py", line 5, in <module>
    user.name
AttributeError: 'NoneType' object has no attribute 'name'

The traceback tells us where the error happened and what kind of exception it was:

  • AttributeError tells us we tried to access a missing attribute.
  • NoneType tells us the object was None.
  • name is the attribute we tried to access.

When I investigate an error like this, I identify the exception, read its message, find the relevant line, and trace how the program reached that state.

Conclusion

Learning Python from Ruby feels fairly natural. The languages share many concepts, but their differences in behavior are worth learning. The ones I especially want to remember are:

  • Indentation as part of the syntax.
  • Explicit return statements.
  • Differences in truthy and falsy values.
  • Lists and tuples.
  • What happens when a dictionary key is missing.
  • How mutable default arguments are evaluated.
  • Differences in exceptions.

Knowing these differences lets me carry much of what I learned in Ruby over to Python, avoid common mistakes, and better understand how Python code is usually written.

Part two: variables, classes, and objects →

Until next time,
Gastón Ramos

 ::: If you'd like to comment, email me: ramos.gaston AT gmail.com :::