00 — Start here¶
Level: 101 · read this first
This library is for someone who writes Python and is learning Ruby, someone who writes Ruby and wants to see what Python does with the same idea, and anyone who has been told "everything is an object" or "a block is not a closure" and would rather watch it happen than take it on trust. Every page asks one question of both languages and prints both answers.
What it assumes¶
That you can read a short program in at least one of the two languages. Nothing else: a page that needs an idea from another page links it, and a page that needs an idea about text, encodings or regular expressions links the Ruby text library ↗, which owns those subjects.
How to read a page¶
Each has the same shape: a one-line claim, the idea in prose, an output block from a Ruby program in that page's examples/ folder, a reading of that output, then Compared with Python — the same question put to Python by a second program, with its output beside the first — then an exercise with a solution you can unfold, and links. The output blocks are generated, never typed: if a page shows a value, Ruby 4.0 or Python 3 printed it, on Ubuntu and on macOS, and the build fails the day it stops.
Two programs per page is the point. A comparison you remember is a claim; a comparison you can run is a measurement, and the two languages are close enough that memory gets it wrong more often than you would think — which of them rounds 2.5 up, which one leaks a loop variable, which one lets you index past the end of an array.
The eight to read first¶
nil,falseand truthiness — the first thing a Python programmer gets wrong in Ruby:0and""are true.- Blocks are not objects — the construct Ruby has and Python does not, and what
yieldactually does. - Default arguments are evaluated on each call — the mutable-default trap, present in one language and absent in the other.
- Hashes and default values —
nilinstead ofKeyError, and the shared-default trap both languages have. privatemeans no receiver — visibility that is enforced, against visibility that is a naming convention.StandardErroris the default — what a barerescuecatches, and what a bareexcept:catches.case/inpattern matching — the two languages added the same feature in the same year with different spellings.- Gotchas for Python programmers — every trap on one page, one measured line each, and its mirror image for the other direction.
Then go by chapter, or by the crosswalk, which puts every idea in one table with its Ruby and Python spellings, or by Topics A–Z.
Where the two languages agree¶
More than either community says. Both are dynamically typed, garbage-collected, object-based languages with duck typing, first-class functions, ordered hashes, unbounded integers, IEEE floats, generators, a global interpreter lock and a match statement. Assignment shares objects in both (Variables are references); integer division floors toward negative infinity in both, and % takes the sign of the divisor in both (Integer division floors); and/or return an operand rather than a boolean in both (and/or precedence); a for loop leaks its variable in both (for loops do not scope); containers print their elements with the programmer-facing form in both (to_s, inspect and p).
Where they differ, and which side surprises whom¶
Ruby will surprise a Python programmer with:
0,""and[]are truthy —nil,falseand truthiness.a[10]on a three-element array isnil, not an error — Arrays and negative indexes.7 / 2is3— Integer division floors.- Strings are mutable — Strings are mutable.
"a"and:aare different hash keys, and{"a": 1}makes the symbol — String and symbol keys.x = false or trueleavesxfalse —and/orprecedence.putsreturnsnil, so a method that ends withputsreturns nothing — The last expression is the value.2.5.roundis3— Floats and rounding.- A class is never finished: anyone can reopen
String— Classes are open. Thread.current[:x]is local to the fiber, and anEnumerator#nextruns on one — Thread locals are fiber locals.
Python will surprise a Ruby programmer with:
def f(a=[])shares one list across every call — Default arguments are evaluated on each call.- Every lambda made inside a loop returns the loop's last value — Closures capture variables.
round(2.5)is2— Floats and rounding.list.sort()returnsNone—!and?methods.- A
matchwith no matchingcasedoes nothing at all —NoMatchingPatternError. {1: "a"}[1.0]finds the value; Ruby's{1 => "a"}[1.0]isnil— ComparingIntegerandFloat.- Defining
__eq__silently makes a class unhashable —eql?andhash. str(10**5000)raises — Integers are unbounded.- There is no
unless, nountil, no do-while, and no trailingif—unless,untiland modifiers.
The pattern: Ruby has two spellings where Python has one — lambda and proc, && and and, == and eql?, dup and clone — and the second spelling is where a Python programmer's model breaks. Python has two types where Ruby has one — str and bytes, list and tuple, set and frozenset — and the second type is where a Ruby programmer's model breaks. The crosswalk has a row for each pair.
The advanced half¶
Chapters 07 to 12 are where the designs diverge most: the object model (singleton classes, allocate, refinements), metaprogramming (define_method, method_missing, hooks, a DSL), under the hood (Binding, TracePoint, the garbage collector, the bytecode, the JITs) and concurrency (threads under the GVL, fibers, Ractors, processes). Each of those pages still carries a Python twin, because __init_subclass__, __getattr__, sys.settrace, dis and multiprocessing are the nearest Python has, and seeing exactly how near is the lesson.