Skip to content

The crosswalk

Level: reference · for anyone who knows one of the two languages

One line: One idea per row — what Ruby calls it, what Python calls it, and the page here that runs both — so a concept you already understand in one language can be looked up rather than relearned.

Every row names an idea once and then gives its two spellings. The last column is this library's lesson on it, where a Ruby program and its Python twin print the answer side by side; where the Python library ↗ has its own page on the idea, that page is linked too, marked ↗. A dash means the language has no counterpart, which is itself the answer. The rows are grouped the way the chapters are; Topics A–Z has the same pages under every name you might search for.

Values and objects

The idea Ruby Python The page here
Nothing nil — an object of NilClass; only nil and false are falsy None — and 0, "", [], {} are falsy too nil, false and truthiness
Everything is an object 1.class, nil.class, Class.class, 1.+(2) type(1), type(None), type(int); operators are dunder methods Everything is an object
A name as a value :symbol — one object per name an interned str, or enum.Enum Symbols
Identity and value equal? · == · eql? · === is · == · __hash__ · nothing like === Four kinds of equality
Assignment shares the object; dup copies shares the object; copy.copy copies Variables are references
Immutability freeze, frozen?, FrozenError; literals are chilled by type: tuple, frozenset, dataclass(frozen=True) freeze
Strings mutable — << changes the object immutable — += makes a new one; str is not bytes ↗ Strings are mutable
Duck typing respond_to?, to_str/to_ary/to_proc hasattr, __iter__/__index__, Protocol Duck typing

Methods and arguments

The idea Ruby Python The page here
Calling parentheses optional; a bare name may be a call parentheses mandatory; a bare name is a reference Parentheses are optional
What a method returns its last expression None unless return says otherwise The last expression is the value
Parameters a, b = 1, *rest, key:, **opts, &blk a, b=1, *args, key, **kwargs, with / and * markers Arguments
Default arguments evaluated on every call evaluated once, at def — the mutable-default trap Defaults are evaluated each call
Naming sort! and empty? sort() returns None; is_ prefixes; no ? ! and ?
Operators methods: def +(other), def <=>(other) dunders: __add__, __lt__, __radd__ Operators are methods
The receiver implicit self explicit self parameter self is implicit
A call on nothing x&.length x.length if x else None, getattr(x, "y", None) Safe navigation
Setters def x=(v); the assignment's value is the argument @property + .setter; assignment is a statement Setters return the argument
A method as a value method(:f), UnboundMethod#bind obj.f (bound), Cls.f (a plain function) Method objects

Blocks, procs and lambdas

The idea Ruby Python The page here
Passing code to a method a block, run with yield a callable argument Blocks are not objects
Anonymous functions lambda / -> (strict) and proc (lenient) lambda — one expression — or a nested def Procs and lambdas differ
Closures share outer variables; a block parameter is fresh per call nonlocal; loop variables bind late Closures capture variables
A method by name &:upcase str.upper, operator.methodcaller &:symbol
The implicit parameter it, _1 — it and numbered parameters
Leaving a block next, break, return continue, break, return next, break and return
Writing an iterator each + Enumerable, Enumerator.new { \|y\| … } a generator function with yield Writing an iterator
Partial application curry, >>, << functools.partial; no composition operator Currying and composition

Collections

The idea Ruby Python The page here
Arrays a[10] is nil; a[1, 2] is start and length a[10] raises; a[1:3] is start and stop — Slicing is not indexing ↗ Arrays and negative indexes
Hashes ordered; h[:x] is nil; Hash.new(0); fetch ordered; d["x"] raises; defaultdict; .get Hashes and default values
Ranges 1..5 inclusive, 1...5 exclusive, endless 1.. range(1, 6), half-open only Ranges
Sets Set — core in 4.0, keeps insertion order {1, 2}, frozenset — unordered Set is a core class
Unpacking a, *b = arr; block parameters auto-splat a, *b = seq; for k, v in … Destructuring
Records Struct (mutable), Data (immutable) namedtuple, dataclass, dataclass(frozen=True) Struct and Data
Ordering <=> and Comparable __lt__ and functools.total_ordering Comparable and <=>
The iteration protocol each + the Enumerable mixin __iter__ + builtins; collections.abc.Sequence as a mixin Enumerable is a mixin
The toolbox map, select, group_by, tally, each_slice, zip comprehensions, itertools.groupby, Counter, batched, zip The Enumerable toolbox
Folding inject, sum (compensated), each_with_object functools.reduce, sum, math.fsum inject and each_with_object
Laziness opt in with lazy generators are lazy by default Lazy enumerators
External iteration Enumerator#next, StopIteration next(it), StopIteration External enumerators
Sorting sort_by; stability not guaranteed sorted(key=); stable — Sorting is not comparing ↗ Sort stability and sort_by

Classes and the object model

The idea Ruby Python The page here
Attributes @x is private; attr_accessor opens it attributes are public; @property computes one Instance variables are private
Visibility private means no explicit receiver; protected _x by convention; __x is mangled private means no receiver
Open classes reopen String and add a method builtins are closed; user classes are open Classes are open
Mixins include, extend, prepend multiple inheritance and the MRO Mixins
Lookup ancestors, super __mro__, super() Method lookup and super
Constants lexical scope, then ancestors UPPER_CASE by convention; Final has no runtime Constants and lexical scope
Class-level state @@x is shared with subclasses; class-level @x is not class attributes, shadowed by instance assignment Class variables are shared
Namespaces module Shop; class Cart packages and modules Modules as namespaces
Per-object methods the singleton class, class << self types.MethodType, classmethod, the metaclass Singleton classes
Construction new = allocate + initialize __new__ + __init__ new, allocate and initialize
Display to_s / inspect; puts / p __str__ / __repr__; print / repr — repr is not str ↗ to_s, inspect and p
Being a hash key eql? + hash __eq__ + __hash__ eql? and hash
Scoped patches refinements — (unittest.mock.patch is the nearest) Refinements
The root of everything BasicObject; Kernel object; the builtins module BasicObject and Kernel
Copies dup, clone, Marshal round-trip copy.copy, copy.deepcopy, pickle dup, clone and frozen state

Errors and exceptions

The idea Ruby Python The page here
Handling begin/rescue/else/ensure; retry try/except/else/finally; no retry rescue, ensure, else and retry
The default catch StandardError Exception; a bare except: is BaseException — Ctrl-C is a signal ↗ StandardError is the default
Raising raise "msg", raise Klass, "msg" raise Klass("msg") raise has four forms
Custom classes class AppError < StandardError class AppError(Exception); exception groups Custom exception classes
Chaining cause, set automatically __context__ automatically; raise … from Exceptions have a cause
Expression-level handling expr rescue default — (contextlib.suppress) rescue as a modifier
Non-local exit throw / catch an exception, or for … else throw and catch
Exiting exit, at_exit, SystemExit sys.exit, atexit, SystemExit exit, at_exit and SystemExit
Warnings warn, -w, Warning[:deprecated] warnings.warn, -W, DeprecationWarning Warnings and -w
The messages you will meet NoMethodError for nil, ArgumentError … AttributeError for None, TypeError … Common error messages

Control flow and pattern matching

The idea Ruby Python The page here
Statements and expressions everything is an expression statements; the conditional expression and := Everything is an expression
Negated and trailing forms unless, until, x if c, begin … end while if not, while; for … else unless, until and modifiers
Boolean operators &&/\|\| and the low-precedence and/or and/or; chained comparisons and/or precedence
Switching on a value case/when with === match with class patterns, or if/elif case/when uses ===
Structural matching case/in, => match/case case/in pattern matching
Variables in patterns ^pin compares; a bare name binds dotted names compare; a bare name binds Pins, guards and alternatives
Matching your own class deconstruct, deconstruct_keys __match_args__ deconstruct and deconstruct_keys
When nothing matches NoMatchingPatternError silently nothing NoMatchingPatternError
Loop scope for leaks its variable; a block does not for leaks; a comprehension does not for loops do not scope

Metaprogramming and under the hood

The idea Ruby Python The page here
Calling by name send, public_send getattr(obj, name)(…) send and public_send
Defining methods at runtime define_method setattr(Cls, name, func) define_method
Ghost methods method_missing, respond_to_missing? __getattr__ method_missing
Evaluating in a context instance_eval, class_eval exec with explicit namespaces instance_eval and class_eval
Hooks inherited, included, method_added __init_subclass__, __set_name__, metaclasses Hooks
A small language instance_eval and blocks class bodies, decorators, with Building a DSL
Classes at runtime Class.new(Base) { … } type(name, bases, namespace) Classes at runtime
Looking inside methods, instance_variables, defined? dir, vars, inspect Introspection
A scope as an object Binding, eval locals(), eval(expr, globals, locals) Binding and eval
Tracing TracePoint sys.settrace, sys.monitoring TracePoint
Memory ObjectSpace, GC, WeakRef gc, weakref ObjectSpace and GC
Bytecode RubyVM::InstructionSequence — YARV dis, code objects The bytecode you can see
JITs YJIT, ZJIT the 3.13 JIT; free-threading JITs and the interpreter

Concurrency and parallelism

The idea Ruby Python The page here
Threads Thread, the GVL threading, the GIL Threads and the GVL
Locks and queues Mutex, Queue, ConditionVariable Lock, queue.Queue, Condition Mutex, Queue and ConditionVariable
Coroutines Fiber generators, async def Fibers are coroutines
Where an enumerator runs on a Fiber as a suspended frame Enumerators run on fibers
Per-thread state Thread#[] (fiber-local!), thread_variable_get threading.local, contextvars Thread locals are fiber locals
Isolated parallelism Ractor multiprocessing; subinterpreters Ractors share nothing
Processes fork, spawn, Process.wait os.fork, subprocess, os.waitpid Processes
Timeouts Timeout.timeout, Thread#kill join(timeout), futures with timeout= Timeouts and killing threads
A queue that refuses when full SizedQueue#push(job, true) raises ThreadError queue.Queue(maxsize=n).put_nowait raises queue.Full A SizedQueue that refuses

IO, files and the system

The idea Ruby Python The page here
Files File.open(p) { \|f\| … } with open(p) as f: — Opening a file ↗ File.open with a block
Printing puts, print, p, pp print, repr, pprint puts, print, p and pp
Arguments and environment ARGV, ENV, $0 sys.argv, os.environ, __name__ ARGV, ENV and the exit status
Other programs system, backticks, Open3 subprocess.run Running other programs
Standard input gets, $stdin, ARGF input(), sys.stdin, fileinput — Standard in, standard out, and pipes ↗ gets and ARGF
Paths Pathname, Dir.glob pathlib.Path — Filenames are not text ↗ Pathname, Dir and glob
Serialization JSON, YAML, Marshal json, no YAML, pickle, tomllib JSON, YAML and Marshal
Time Time, Date datetime, date, timedelta Time and date
Formatting "#{x}", format, % f-strings, format, % — The format mini-language ↗ String formatting
Text and encodings one String type carrying an encoding label — the Ruby text library ↗ str and bytes — str is not bytes ↗ Text lives in the text library

Tooling, testing and gems

The idea Ruby Python The page here
Loading code require, require_relative, load import, importlib.reload require, require_relative and load
Packages gems, Bundler, Gemfile pip, pyproject.toml, venv — pyproject.toml ↗ Gems, Bundler and the Gemfile
Tests Minitest unittest Minitest and unittest
The command line ruby -e -c -w -n -p python3 -c -m -I -W — -c is not the prompt ↗ The command-line flags
The REPL irb python3 -i, >>> irb and the REPL
Versions 1.9 → 4.0 3.8 → 3.14 Ruby versions — what changed
Backtraces caller, backtrace traceback, inspect.stack Backtraces and caller
Documentation comments, YARD, ri — no docstrings __doc__, help() No docstrings

Numbers

The idea Ruby Python The page here
Integer division 7 / 2 is 3 7 / 2 is 3.5; 7 // 2 is 3 Integer division floors
Big integers one unbounded Integer unbounded int — bin() is not the bits ↗ Integers are unbounded
Floats and rounding 2.5.round is 3 round(2.5) is 2 — Float equality and NaN ↗ Floats and rounding
Exact arithmetic Rational, BigDecimal Fraction, Decimal Rational and BigDecimal
1 == 1.0 true — but {1 => x}[1.0] is nil True — and {1: x}[1.0] finds it — Comparing an int with a float ↗ Comparing Integer and Float
Mixed arithmetic coerce __radd__ and NotImplemented Numeric coercion
Randomness Random.new(seed), SecureRandom random.Random(seed), secrets Random with a seed
Formatting numbers format, to_s(2); no thousands flag format, bin, f"{n:,}" Number formatting

Idioms and gotchas

The idea Ruby Python The page here
The philosophy more than one way to do it one obvious way The Ruby way and the Pythonic way
Defaults x \|\|= value x = x or value, :=, setdefault \|\|= and nil guards
Pipelines tap, then — tap, then and chaining
Several results an Array a tuple Multiple return values
Keys "a" and :a are different keys one string type String and symbol keys
Style two spaces, snake_case, ?/! PEP 8, four spaces Style and naming
Comments #, =begin, __END__ #, docstrings Comments and documentation
The traps for a Python programmer for a Ruby programmer both pages, one measured line per trap

The pattern across the whole table: where Python has one spelling, Ruby often has two — lambda and proc, && and and, equal?/==/eql?/===, dup and clone, puts and p — and the second spelling is where a Python programmer's model breaks. Where Ruby has one type, Python often has two — str/bytes, list/tuple, set/frozenset — and the second type is where a Ruby programmer's model breaks. Each of those pairs has a row above and a page behind it.