Output & Running
Hello, World
print("Hello, World!") puts "Hello, World!" Both
print() and puts append a newline by default. Ruby's puts is the idiomatic equivalent of Python's print(). Ruby scripts run top-to-bottom with no boilerplate class or main function required.Output functions
message = "hello"
print(message) # with newline
print(message, end="") # without newline
print(repr(message)) # developer representation message = "hello"
puts message # with newline
print message # without newline
puts # blank line
p message # developer representation Ruby's
puts corresponds to Python's print(), print to print(..., end=""), and p to print(repr(...)). Note that Ruby's print (no newline) is a different method from Python's print().Comments
# Single-line comment
# Python has no built-in multi-line comment syntax.
# Use consecutive # lines for block comments.
x = 42 # inline comment # Single-line comment
# Ruby also has no multi-line comment syntax in common use.
# Use consecutive # lines for block comments.
# (=begin/=end exists but is rarely used.)
x = 42 # inline comment Both languages use
# for single-line comments. Neither has a widely-used multi-line comment literal. Python has """docstrings""" used as documentation; Ruby has =begin/=end but it is rare in practice.No main guard
# Python: code at top level runs when the file is executed directly
# Use if __name__ == "__main__": to prevent running on import
def greet(name):
return f"Hello, {name}!"
if __name__ == "__main__":
print(greet("World")) # Ruby: code at top level always runs when the file is loaded.
# No import system that re-executes files — require caches the load.
def greet(name)
"Hello, #{name}!"
end
puts greet("World") Ruby has no equivalent of Python's
if __name__ == "__main__": guard. The require system caches loaded files, so you don't need to protect against re-execution. Scripts that both define reusable code and run standalone are common — just put the runnable part at the top level.Formatted output
name = "Alice"
age = 30
print(f"{name} is {age} years old")
print(f"next year: {age + 1}") name = "Alice"
age = 30
puts "#{name} is #{age} years old"
puts "next year: #{age + 1}" Ruby's
#{...} interpolation in double-quoted strings is directly equivalent to Python's f"{...}" f-strings. Both evaluate arbitrary expressions inside the delimiters. Ruby interpolation works in "double" strings and heredocs but not in 'single' strings.Variables & Types
Variable assignment
count = 10
message = "hello"
is_done = True
nothing = None
print(type(count)) # <class 'int'>
print(type(message)) # <class 'str'> count = 10
message = "hello"
is_done = true
nothing = nil
puts count.class # Integer
puts message.class # String Variable assignment is identical — no declarations, no type annotations required. Python's
None becomes Ruby's nil. Python's True/False become Ruby's true/false (lowercase). Use .class instead of type() to inspect the type.None vs nil
value = None
print(value is None) # True
print(value == None) # True (use 'is None' by convention)
def maybe_find(items, target):
for item in items:
if item == target:
return item
return None
result = maybe_find([1, 2, 3], 9)
print(result) # None value = nil
puts value.nil? # true
puts value == nil # true (== is fine in Ruby)
def maybe_find(items, target)
items.find { |item| item == target }
end
result = maybe_find([1, 2, 3], 9)
puts result.inspect # nil Ruby's
nil is an object (of class NilClass) — you can call methods on it. Check for nil with .nil? or == nil; both are idiomatic (unlike Python where is None is preferred over == None). Ruby's Enumerable#find returns nil automatically when nothing matches.Truthiness
# Python: False, None, 0, 0.0, "", [], {}, set() are all falsy
values = [False, None, 0, "", [], {}]
for value in values:
if not value:
print(f"{repr(value)} is falsy") # Ruby: ONLY false and nil are falsy — everything else is truthy
values = [false, nil, 0, "", [], {}]
values.each do |value|
unless value
puts "#{value.inspect} is falsy"
end
end This is one of the most important differences. In Python,
0, "", [], and {} are all falsy. In Ruby, only false and nil are falsy — 0 is truthy, "" is truthy, [] is truthy. This trips up nearly every Python developer coming to Ruby.Type checking
number = 42
text = "hello"
print(type(number)) # <class 'int'>
print(isinstance(number, int)) # True
print(isinstance(text, (str, int))) # True number = 42
text = "hello"
puts number.class # Integer
puts number.is_a?(Integer) # true
puts text.is_a?(String) # true
puts text.is_a?(Numeric) # false Use
.class instead of type() and .is_a? instead of isinstance(). Ruby's integer type is Integer (not int). is_a? respects the inheritance hierarchy, so 42.is_a?(Numeric) is true even though 42.class is Integer.Multiple assignment
# Tuple unpacking
first, second, third = 1, 2, 3
print(first, second, third) # 1 2 3
# Splat in unpacking
head, *tail = [10, 20, 30, 40]
print(head) # 10
print(tail) # [20, 30, 40]
# Swap
a, b = 1, 2
a, b = b, a
print(a, b) # 2 1 # Parallel assignment
first, second, third = 1, 2, 3
puts "#{first} #{second} #{third}" # 1 2 3
# Splat in assignment
head, *tail = [10, 20, 30, 40]
puts head # 10
puts tail.inspect # [20, 30, 40]
# Swap
a, b = 1, 2
a, b = b, a
puts "#{a} #{b}" # 2 1 Ruby's parallel assignment mirrors Python's tuple unpacking almost exactly. The splat operator
* collects remaining values just like Python's * in unpacking. Swapping variables with a, b = b, a works identically in both languages.Constants
# Python: constants are a convention (UPPER_SNAKE_CASE)
# The interpreter does not enforce immutability
MAX_SIZE = 100
PI = 3.14159
MAX_SIZE = 200 # No error — just bad practice
print(MAX_SIZE) # Ruby: constants are enforced — reassignment produces a warning
MAX_SIZE = 100
PI = 3.14159
# MAX_SIZE = 200 # Warning: already initialized constant MAX_SIZE
puts MAX_SIZE
puts PI Ruby enforces constant naming: any identifier starting with a capital letter is a constant, and reassigning it produces a warning. Python's ALL_CAPS convention for constants is purely social — the language does nothing to enforce it. Class and module names in Ruby are also constants (
String, Array, etc.).Strings
Single vs double quotes
name = "Alice"
greeting = 'Hello' # identical to double quotes in Python
# Both support escape sequences
tab_demo = "col1\tcol2"
newline = 'line1\nline2'
print(tab_demo)
print(newline) name = "Alice"
greeting = 'Hello' # single quotes do NOT interpolate
# Single quotes: only \\ and \' are escape sequences
tab_demo = "col1 col2" # double: escape sequences work
literal = 'col1 col2' # single: is literal backslash-t
puts tab_demo
puts literal In Python, single and double quotes are completely interchangeable. In Ruby, single-quoted strings are literal — they do not interpolate
#{} and only recognize \\ and \' as escapes. Double-quoted strings support both interpolation and the full set of escape sequences. Use double quotes when you need interpolation or escapes; single quotes for simple string literals.f-strings vs interpolation
name = "Alice"
age = 30
# f-strings (Python 3.6+)
print(f"Name: {name}, Age: {age}")
print(f"In 5 years: {age + 5}")
print(f"Upper: {name.upper()}") name = "Alice"
age = 30
# String interpolation (double-quoted strings)
puts "Name: #{name}, Age: #{age}"
puts "In 5 years: #{age + 5}"
puts "Upper: #{name.upcase}" Ruby's
#{...} interpolation predates Python's f-strings and works identically — any expression is valid inside the braces. Note that Ruby uses .upcase (no parentheses needed for no-argument methods) rather than Python's .upper().Multiline strings
# Triple-quoted strings
poem = """
Roses are red,
Violets are blue,
Python is cool,
And Ruby is too.
"""
print(poem.strip()) # Heredoc (squiggly heredoc strips leading whitespace)
poem = <<~POEM
Roses are red,
Violets are blue,
Python is cool,
And Ruby is too.
POEM
puts poem.chomp Ruby's squiggly heredoc (
<<~IDENTIFIER) strips leading whitespace proportionally — similar to Python's textwrap.dedent(). The terminating identifier must be at the start of its own line. Ruby also supports plain < (preserves all whitespace) and interpolating heredocs with double-quoted delimiters. Common string methods
text = " Hello, World! "
print(text.strip()) # "Hello, World!"
print(text.upper()) # " HELLO, WORLD! "
print(text.lower()) # " hello, world! "
print(text.replace(",", ";"))
print("hello world".split()) # ['hello', 'world']
print(len("hello")) # 5 text = " Hello, World! "
puts text.strip # "Hello, World!"
puts text.upcase # " HELLO, WORLD! "
puts text.downcase # " hello, world! "
puts text.gsub(",", ";")
puts "hello world".split.inspect # ["hello", "world"]
puts "hello".length # 5 Ruby uses
.upcase/.downcase instead of .upper()/.lower(), .gsub instead of .replace(), and .length or .size instead of len(). Methods that take no arguments are called without parentheses by convention. The ! suffix (.strip!, .upcase!) mutates the string in place.String formatting
# % operator (classic)
print("Hello, %s! You are %d years old." % ("Alice", 30))
# format() method
print("{name} scored {score:.2f}".format(name="Bob", score=95.678))
# f-string (modern, preferred)
score = 95.678
print(f"Score: {score:.2f}") # % operator (similar to Python's % / C's sprintf)
puts "Hello, %s! You are %d years old." % ["Alice", 30]
# sprintf / format method
puts format("%.2f", 95.678)
puts sprintf("%s scored %.2f", "Bob", 95.678)
# Interpolation (modern, preferred)
score = 95.678
puts "Score: #{"%.2f" % score}" Ruby supports the
% operator for sprintf-style formatting, passing an array as the right operand. The format and sprintf methods work like Python's format(). For most cases, string interpolation (#{"%.2f" % value}) is idiomatic. Ruby does not have a named-placeholder equivalent of Python's .format(name=value).Frozen (immutable) strings
# Python strings are always immutable
text = "hello"
# text[0] = "H" # TypeError: 'str' object does not support item assignment
# To "modify" a string, create a new one
text = text.capitalize()
print(text) # Hello # Ruby 4.0: string literals are frozen by default (immutable)
text = "hello"
# text[0] = "H" # FrozenError: can't modify frozen String
# Create a mutable copy with .dup or use .upcase (returns new string)
text = text.capitalize
puts text # Hello
# Or build a new mutable string
mutable = +"hello" # unary + creates unfrozen copy
mutable[0] = "H"
puts mutable # Hello Python strings have always been immutable. Ruby strings were historically mutable, but Ruby 4.0 froze string literals by default — making Ruby's behavior match Python's. The unary
+ operator (+"string") creates a mutable (unfrozen) copy when you genuinely need in-place mutation.Numbers
Integer arithmetic
print(10 + 3) # 13
print(10 - 3) # 7
print(10 * 3) # 30
print(10 ** 3) # 1000
print(10 % 3) # 1 puts 10 + 3 # 13
puts 10 - 3 # 7
puts 10 * 3 # 30
puts 10 ** 3 # 1000
puts 10 % 3 # 1 Integer arithmetic operators are identical between Python and Ruby, including
** for exponentiation and % for modulo. Both languages support arbitrarily large integers without overflow.Integer vs floor division
# Python: / always returns float, // for integer (floor) division
print(7 / 2) # 3.5 (float)
print(7 // 2) # 3 (floor division)
print(-7 // 2) # -4 (floors toward negative infinity) # Ruby: / between two integers returns an integer (truncates toward zero)
puts 7 / 2 # 3 (integer division)
puts 7.0 / 2 # 3.5 (float when either operand is float)
puts -7 / 2 # -4 (Ruby also floors toward negative infinity) Key difference: Python's
/ always produces a float (7/2 == 3.5), requiring // for integer division. Ruby's / on two integers produces an integer (7/2 == 3) — make one operand a float to get a float result. Both floor toward negative infinity for negative numbers.Arbitrary-precision integers
big = 2 ** 100
print(big)
print(type(big)) # <class 'int'>
factorial = 1
for i in range(1, 31):
factorial *= i
print(factorial) # 30! big = 2 ** 100
puts big
puts big.class # Integer
factorial = (1..30).reduce(:*)
puts factorial # 30! Both Python and Ruby support arbitrarily large integers natively — no
BigInteger library needed. Ruby's single Integer type handles both small and large integers automatically (Python's int works the same way). Ruby's (1..30).reduce(:*) is idiomatic for factorial.Numeric conversions
print(int("42")) # 42
print(float("3.14")) # 3.14
print(str(99)) # "99"
print(int(3.9)) # 3 (truncates, does not round)
print(round(3.9)) # 4 puts "42".to_i # 42
puts "3.14".to_f # 3.14
puts 99.to_s # "99"
puts 3.9.to_i # 3 (truncates, does not round)
puts 3.9.round # 4 Ruby uses method calls for conversions:
.to_i (to integer), .to_f (to float), .to_s (to string). Python uses constructor-style functions: int(), float(), str(). Both truncate toward zero when converting a float to integer.Lists / Arrays
Creation and access
numbers = [10, 20, 30, 40, 50]
print(numbers[0]) # 10 (first)
print(numbers[-1]) # 50 (last)
print(numbers[1:3]) # [20, 30]
print(len(numbers)) # 5 numbers = [10, 20, 30, 40, 50]
puts numbers[0] # 10 (first)
puts numbers[-1] # 50 (last)
puts numbers[1, 2].inspect # [20, 30] (start, length)
puts numbers[1..2].inspect # [20, 30] (range)
puts numbers.length # 5 Array indexing works the same — zero-based, negative indices count from the end. Slicing differs: Python uses
list[start:end] (exclusive end), while Ruby uses either a Range (array[1..2] inclusive) or a start-plus-length form (array[1, 2]). Use .length or .size instead of len().Common mutation methods
items = [1, 2, 3]
items.append(4) # [1, 2, 3, 4]
items.insert(0, 0) # [0, 1, 2, 3, 4]
items.remove(2) # [0, 1, 3, 4]
popped = items.pop() # 4 removed
print(items) # [0, 1, 3] items = [1, 2, 3]
items.push(4) # [1, 2, 3, 4] (also: items << 4)
items.unshift(0) # [0, 1, 2, 3, 4]
items.delete(2) # [0, 1, 3, 4]
popped = items.pop # 4 removed
puts items.inspect # [0, 1, 3] Python's
.append() is Ruby's .push() or the shovel operator <<. Python's .insert(0, x) is Ruby's .unshift(x). Python's .remove(value) is Ruby's .delete(value). Ruby's .pop and Python's .pop() both remove and return the last element.Higher-order operations
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
print(sorted(numbers)) # [1, 1, 2, 3, 4, 5, 6, 9]
print(list(reversed(numbers))) # [6, 2, 9, 5, 1, 4, 1, 3]
print(numbers.count(1)) # 2
print(sum(numbers)) # 31
print(min(numbers), max(numbers)) numbers = [3, 1, 4, 1, 5, 9, 2, 6]
puts numbers.sort.inspect # [1, 1, 2, 3, 4, 5, 6, 9]
puts numbers.reverse.inspect # [6, 2, 9, 5, 1, 4, 1, 3]
puts numbers.count(1) # 2
puts numbers.sum # 31
puts "#{numbers.min} #{numbers.max}" Python uses standalone functions (
sorted(), reversed(), sum(), min(), max()) for many operations on lists. Ruby defines these as methods directly on the array: .sort, .reverse, .sum, .min, .max. Ruby's .sort returns a new array (like Python's sorted()); use .sort! to sort in place.Array operators
a = [1, 2, 3]
b = [4, 5, 6]
print(a + b) # [1, 2, 3, 4, 5, 6] concatenation
print(a * 3) # [1, 2, 3, 1, 2, 3, 1, 2, 3] repeat
print(3 in a) # False
print(4 in b) # True a = [1, 2, 3]
b = [4, 5, 6]
puts (a + b).inspect # [1, 2, 3, 4, 5, 6]
puts (a * 3).inspect # [1, 2, 3, 1, 2, 3, 1, 2, 3]
puts a.include?(3) # true
puts b.include?(4) # true + for concatenation and * for repetition work the same in both languages. Python's in operator becomes Ruby's .include? method. Ruby also has set-like operators: & (intersection), | (union), and - (difference) on arrays.Dicts / Hashes
Creation and access
person = {"name": "Alice", "age": 30, "city": "NYC"}
print(person["name"]) # Alice
print(person.get("name")) # Alice
print(person.get("country", "US")) # US (default) person = { name: "Alice", age: 30, city: "NYC" }
puts person[:name] # Alice
puts person.fetch(:name) # Alice
puts person.fetch(:country, "US") # US (default) Ruby hashes commonly use symbols as keys (
:name) rather than strings. The { key: value } syntax is shorthand for { :key => value }. Use .fetch(key) for stricter access (raises KeyError if missing) or .fetch(key, default) for a default — equivalent to Python's dict.get(key, default). Accessing a missing key returns nil (not a KeyError).Iteration
scores = {"Alice": 95, "Bob": 87, "Carol": 92}
for name, score in scores.items():
print(f"{name}: {score}")
print(list(scores.keys()))
print(list(scores.values())) scores = { alice: 95, bob: 87, carol: 92 }
scores.each do |name, score|
puts "#{name}: #{score}"
end
puts scores.keys.inspect
puts scores.values.inspect Python's
dict.items() becomes hash.each with a two-argument block. dict.keys() and dict.values() work the same in Ruby (without parentheses). Ruby iterates in insertion order, as Python 3.7+ dicts do.Merge and transform
defaults = {"color": "red", "size": "medium"}
overrides = {"size": "large", "weight": "heavy"}
# Merge (Python 3.9+: | operator)
merged = defaults | overrides
print(merged)
# Dict comprehension
squared = {k: v**2 for k, v in {"a": 2, "b": 3}.items()}
print(squared) defaults = { color: "red", size: "medium" }
overrides = { size: "large", weight: "heavy" }
# Merge (right side wins)
merged = defaults.merge(overrides)
puts merged.inspect
# Transform values
squared = { a: 2, b: 3 }.transform_values { |value| value ** 2 }
puts squared.inspect Ruby's
.merge is equivalent to Python 3.9's | operator or {**a, **b} — the right-hand hash wins on conflicts. Ruby 2.4+ has .transform_values and .transform_keys for mapping over hashes without manually building a new one.Default values
from collections import defaultdict
# defaultdict — missing key creates a default value
word_count = defaultdict(int)
words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
for word in words:
word_count[word] += 1
print(dict(word_count)) # Hash.new with a default block
word_count = Hash.new(0)
words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
words.each { |word| word_count[word] += 1 }
puts word_count.inspect
# Or use tally (Ruby 2.7+)
puts words.tally.inspect Python's
defaultdict requires an import; Ruby's equivalent is Hash.new(default_value) or Hash.new { |hash, key| hash[key] = computed_default }. Ruby 2.7 added .tally, which counts occurrences in a collection — a one-liner replacement for the word-count pattern.Ranges
Range creation
# range() is exclusive of the end
numbers = list(range(1, 11)) # 1 through 10
print(numbers)
for number in range(5):
print(number, end=" ") # .. is inclusive, ... is exclusive
numbers = (1..10).to_a # 1 through 10
puts numbers.inspect
(0...5).each { |number| print "#{number} " }
puts Python's
range(1, 11) is exclusive of 11, equivalent to Ruby's 1...11 (three dots, exclusive end). Ruby's 1..10 (two dots) is inclusive of 10. Ruby ranges are objects — they respond to methods like .each, .to_a, .include?, and .step.Step and iteration
# range with step
for number in range(0, 20, 3):
print(number, end=" ")
print()
# Descending
for number in range(10, 0, -1):
print(number, end=" ") # step method
(0..19).step(3) { |number| print "#{number} " }
puts
# Descending
10.downto(1) { |number| print "#{number} " }
puts
# Or: step with negative
(10).downto(1).each { |number| print "#{number} " } Ruby's
.step(n) on a range mirrors range(start, stop, step). For descending iteration, Ruby offers the expressive 10.downto(1) and 1.upto(10) methods on integers, in addition to .step(-1).Range membership and case
score = 85
if 90 <= score <= 100:
grade = "A"
elif 80 <= score < 90:
grade = "B"
elif 70 <= score < 80:
grade = "C"
else:
grade = "F"
print(grade) score = 85
grade = case score
when 90..100 then "A"
when 80..89 then "B"
when 70..79 then "C"
else "F"
end
puts grade Ruby's
case/when with ranges is a natural fit for grade-boundary logic. The range membership check (80..89 === score) is implicit. Python's chained comparison (80 <= score < 90) is readable but requires a full if/elif chain — Ruby's case/when is more concise for this pattern.Control Flow
if / elif / else
temperature = 22
if temperature > 30:
print("Hot")
elif temperature > 20:
print("Warm")
elif temperature > 10:
print("Cool")
else:
print("Cold") temperature = 22
if temperature > 30
puts "Hot"
elsif temperature > 20
puts "Warm"
elsif temperature > 10
puts "Cool"
else
puts "Cold"
end Ruby uses
elsif (not elif and not else if). Blocks are delimited by end, not by indentation. Parentheses around the condition are optional. Python's significant indentation forces one style; Ruby is indentation-agnostic (though 2-space indent is conventional).unless (no Python equivalent)
# Python has no 'unless' — use 'if not' or 'if x is None'
logged_in = False
if not logged_in:
print("Please log in")
x = None
if x is None:
print("x is not set") logged_in = false
unless logged_in
puts "Please log in"
end
x = nil
puts "x is not set" if x.nil?
puts "Please log in" unless logged_in Ruby has
unless condition as a shorthand for if !condition — it reads more naturally for negative conditions. Use it sparingly; avoid unless ... else (just flip to if). The postfix forms statement if condition and statement unless condition are idiomatic for single-line guards.Ternary expression
age = 20
status = "adult" if age >= 18 else "minor"
print(status)
# Python's ternary: value_if_true if condition else value_if_false age = 20
status = age >= 18 ? "adult" : "minor"
puts status
# Ruby's ternary: condition ? value_if_true : value_if_false Python's ternary reads left-to-right:
value if condition else other. Ruby uses the C-style condition ? value : other. Both are expressions that return a value. Python's form is often considered more readable for simple cases; Ruby's is more familiar to developers from C/Java/JavaScript backgrounds.case / when
# Python 3.10+: match / case
command = "quit"
match command:
case "quit" | "exit":
print("Goodbye!")
case "help":
print("Available commands: quit, help")
case _:
print(f"Unknown command: {command}") command = "quit"
case command
when "quit", "exit"
puts "Goodbye!"
when "help"
puts "Available commands: quit, help"
else
puts "Unknown command: #{command}"
end Ruby's
case/when predates Python's match/case (added in Python 3.10). Ruby's when accepts a comma-separated list of alternatives. Under the hood, Ruby uses === for matching — which means when can match against Regexps, Ranges, classes, and any object that implements ===.Postfix if / unless
# Python has no postfix 'if' — always prefix
x = 42
if x > 0:
print("positive")
items = []
if not items:
print("empty list") x = 42
puts "positive" if x > 0
items = []
puts "empty list" if items.empty?
# unless is also available postfix
puts "not zero" unless x.zero? Ruby's postfix
if and unless are idiomatic for single-condition guards. Python has no equivalent. The Ruby style guide recommends postfix for simple, short conditions — it reads like English: "do this if that". Avoid postfix when the condition is complex or when the statement is already long.Loops & Iteration
for loop vs each
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
print(fruit) fruits = ["apple", "banana", "cherry"]
fruits.each do |fruit|
puts fruit
end
# Shorthand for single-expression blocks
fruits.each { |fruit| puts fruit } Ruby has no
for/in loop in common use — iteration is always via methods like .each, .map, .select, etc. This makes Ruby's iteration object-oriented: .each is a method call on the collection, and the block do |x| ... end or { |x| ... } is a closure passed to it.while / until
count = 0
while count < 5:
print(count)
count += 1
# Python has no 'until' keyword
count = 10
while count != 0:
count -= 1
print("done") count = 0
while count < 5
puts count
count += 1
end
# Ruby has 'until' — reads more naturally for some conditions
count = 10
until count.zero?
count -= 1
end
puts "done" Both use
while for conditional loops. Ruby also provides until condition as the opposite of while — equivalent to while !condition. Note that Ruby lacks += and -= compound operators for some types, but they work on integers just as in Python.break / continue vs break / next
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for number in numbers:
if number % 2 == 0:
continue # skip even numbers
if number > 7:
break # stop at 7
print(number) numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
numbers.each do |number|
next if number.even? # skip even numbers
break if number > 7 # stop at 7
puts number
end Python's
continue becomes Ruby's next — it skips to the next iteration. break works the same in both. In Ruby, next and break work inside any block, not just traditional loops — so they work with .each, .map, and other iterators.enumerate() vs each_with_index
fruits = ["apple", "banana", "cherry"]
for index, fruit in enumerate(fruits):
print(f"{index}: {fruit}")
# Custom start index
for index, fruit in enumerate(fruits, start=1):
print(f"{index}: {fruit}") fruits = ["apple", "banana", "cherry"]
fruits.each_with_index do |fruit, index|
puts "#{index}: #{fruit}"
end
# Note: index comes SECOND in Ruby's block arguments
fruits.each_with_index { |fruit, index| puts "#{index + 1}: #{fruit}" } Ruby's
.each_with_index passes the element first and index second — the opposite order from Python's enumerate() tuple unpacking. Ruby also has .each_with_object for accumulating results, and .map.with_index for indexed transformation.zip()
names = ["Alice", "Bob", "Carol"]
scores = [95, 87, 92]
for name, score in zip(names, scores):
print(f"{name}: {score}") names = ["Alice", "Bob", "Carol"]
scores = [95, 87, 92]
names.zip(scores).each do |name, score|
puts "#{name}: #{score}"
end Ruby's
.zip is a method on the first array, taking the other arrays as arguments. It returns an array of arrays. The pattern a.zip(b).each { |x, y| ... } is equivalent to Python's for x, y in zip(a, b):. Ruby also has .each_with_object for building up results during iteration.Comprehensions vs Enumerable
List comprehension vs map
numbers = [1, 2, 3, 4, 5]
# List comprehension
doubled = [n * 2 for n in numbers]
print(doubled)
# map() with lambda
doubled2 = list(map(lambda n: n * 2, numbers))
print(doubled2) numbers = [1, 2, 3, 4, 5]
# map with a block (preferred)
doubled = numbers.map { |number| number * 2 }
puts doubled.inspect
# Symbol#to_proc shorthand for named methods
words = ["hello", "world"]
puts words.map(&:upcase).inspect Ruby uses
.map where Python uses list comprehensions or map(). The block syntax { |x| x * 2 } is roughly equivalent to a lambda. Ruby's &:method_name shorthand (map(&:upcase)) converts a symbol to a proc — a convenient replacement for map(lambda s: s.upper()).Filter comprehension vs select
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# List comprehension with condition
evens = [n for n in numbers if n % 2 == 0]
print(evens)
# filter() with lambda
odds = list(filter(lambda n: n % 2 != 0, numbers))
print(odds) numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = numbers.select { |number| number.even? }
puts evens.inspect
odds = numbers.reject { |number| number.even? }
puts odds.inspect Ruby uses
.select (keep elements where block returns true) and .reject (drop elements where block returns true). Python has both list comprehensions with conditions and filter(). Note Ruby's predicate methods: .even?, .odd?, .zero?, .nil?, .empty? — the ? suffix signals a boolean-returning method.reduce / inject
from functools import reduce
numbers = [1, 2, 3, 4, 5]
total = reduce(lambda acc, n: acc + n, numbers, 0)
print(total) # 15
product = reduce(lambda acc, n: acc * n, numbers)
print(product) # 120 numbers = [1, 2, 3, 4, 5]
total = numbers.reduce(0) { |accumulator, number| accumulator + number }
puts total # 15
# Symbol shorthand
product = numbers.reduce(:*)
puts product # 120
# inject is an alias for reduce
puts numbers.inject(:+) # 15 Ruby's
.reduce (also aliased as .inject) corresponds to Python's functools.reduce. The symbol shorthand .reduce(:+) is equivalent to passing the + method as an operator — cleaner than Python's lambda acc, n: acc + n. An initial value is the first argument: .reduce(0, :+).any() / all()
numbers = [2, 4, 6, 8, 10]
print(all(n % 2 == 0 for n in numbers)) # True
print(any(n > 9 for n in numbers)) # True
print(any(n < 0 for n in numbers)) # False numbers = [2, 4, 6, 8, 10]
puts numbers.all?(&:even?) # true
puts numbers.any? { |n| n > 9 } # true
puts numbers.any? { |n| n < 0 } # false
puts numbers.none? { |n| n < 0 } # true Ruby's
.all?, .any?, and .none? mirror Python's all(), any(), and not any(). Python uses generator expressions; Ruby uses blocks. The ? suffix signals a predicate method that returns a boolean.Method chaining
numbers = range(1, 11)
# Chaining: filter then transform then sum
result = sum(n * n for n in numbers if n % 2 == 0)
print(result) # 220 (4+16+36+64+100)
# Or with explicit steps
evens = [n for n in numbers if n % 2 == 0]
squared = [n * n for n in evens]
total = sum(squared)
print(total) result = (1..10)
.select(&:even?)
.map { |number| number ** 2 }
.sum
puts result # 220
# Short form
puts (1..10).select(&:even?).map { |number| number**2 }.sum Ruby's chaining style — calling methods sequentially — is central to idiomatic Ruby. Each Enumerable method returns an array (or enumerator), enabling further chaining. Python achieves similar results with nested comprehensions or explicit variables, but Ruby's method chain reads left-to-right like prose.
Functions
def and return values
def add(a, b):
return a + b
def greet(name):
message = f"Hello, {name}!"
return message
print(add(3, 4))
print(greet("Alice")) def add(first, second)
first + second # last expression is the return value
end
def greet(name)
message = "Hello, #{name}!"
message # returned implicitly
end
puts add(3, 4)
puts greet("Alice") Ruby methods return the value of their last expression implicitly — no
return keyword needed (though it is valid for early returns). This encourages writing methods as a sequence of transformations ending with the desired value. The return keyword is used when exiting early from a method.Default parameters
def greet(name, greeting="Hello", punctuation="!"):
print(f"{greeting}, {name}{punctuation}")
greet("Alice") # Hello, Alice!
greet("Bob", "Hi") # Hi, Bob!
greet("Carol", punctuation=".") # Hello, Carol. def greet(name, greeting: "Hello", punctuation: "!")
puts "#{greeting}, #{name}#{punctuation}"
end
greet("Alice") # Hello, Alice!
greet("Bob", greeting: "Hi") # Hi, Bob!
greet("Carol", punctuation: ".") # Hello, Carol. Ruby uses keyword arguments (with colon suffix) as the idiomatic way to provide optional parameters with defaults — similar to Python's keyword arguments. Ruby also supports positional defaults (
def f(x, y=10)). Unlike Python, Ruby does not allow calling keyword-only arguments as positional ones.*args and **kwargs
def variadic(*args, **kwargs):
print(args) # tuple of positional args
print(kwargs) # dict of keyword args
variadic(1, 2, 3, name="Alice", city="NYC")
# Spreading a list/dict into a call
numbers = [1, 2, 3]
options = {"sep": "-"}
print(*numbers) def variadic(*args, **kwargs)
puts args.inspect # array of positional args
puts kwargs.inspect # hash of keyword args
end
variadic(1, 2, 3, name: "Alice", city: "NYC")
# Spreading an array into a call
numbers = [1, 2, 3]
puts numbers.join("-") # 1-2-3
# Splat in method call
def add(first, second, third) = first + second + third
puts add(*numbers) Ruby's
*args collects extra positional arguments as an array (Python collects them as a tuple). Ruby's **kwargs collects keyword arguments as a hash. The splat *array in a method call expands an array into positional arguments — the same as Python's *list spreading.Lambdas and procs
# Lambda
square = lambda x: x ** 2
print(square(5)) # 25
# Multi-line lambda via function
def make_adder(n):
return lambda x: x + n
add5 = make_adder(5)
print(add5(10)) # 15 # Lambda
square = ->(x) { x ** 2 }
puts square.call(5) # 25
puts square.(5) # alternative call syntax
# Multi-line via proc (or lambda)
def make_adder(number)
->(x) { x + number }
end
add5 = make_adder(5)
puts add5.call(10) # 15 Ruby's lambda literal is
->(args) { body }, called with .call() or the shorthand .(). Ruby also has Proc.new { |x| ... } which behaves differently (no strict arity, different return semantics). The -> (stabby lambda) is preferred for lambda-style anonymous functions.One-liner method syntax
# Python: no special one-liner syntax; use lambda for simple cases
double = lambda x: x * 2
is_even = lambda n: n % 2 == 0
# For readable named methods, always use full def
def square(x):
return x * x # Ruby 4.0: one-liner method syntax (no end needed)
def double(x) = x * 2
def square(x) = x * x
def greet(name) = "Hello, #{name}!"
puts double(5) # 10
puts square(4) # 16
puts greet("Alice") # Hello, Alice! Ruby 3.0 introduced a one-liner method definition using
= instead of a body and end. This is equivalent to Python's use of lambda for simple one-expression functions, but produces a proper named method. Use it when the entire body is a single expression.Classes & OOP
Class definition
class Dog:
def __init__(self, name, breed):
self.name = name
self.breed = breed
def speak(self):
return f"{self.name} says Woof!"
dog = Dog("Rex", "Labrador")
print(dog.speak())
print(dog.name) class Dog
def initialize(name, breed)
@name = name
@breed = breed
end
def speak
"#{@name} says Woof!"
end
end
dog = Dog.new("Rex", "Labrador")
puts dog.speak
puts dog.instance_variable_get(:@name) Key differences: (1)
__init__ becomes initialize; (2) self.x = y (Python attribute assignment) becomes @x = y (Ruby instance variable); (3) methods omit the explicit self parameter; (4) call Dog.new(), not Dog(); (5) last expression is returned implicitly.@property vs attr_accessor
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value < 0:
raise ValueError("Radius must be non-negative")
self._radius = value
@property
def area(self):
return 3.14159 * self._radius ** 2
c = Circle(5)
print(c.radius)
print(c.area) class Circle
attr_reader :area
def initialize(radius)
self.radius = radius # calls the setter
end
def radius = @radius
def radius=(value)
raise ArgumentError, "Radius must be non-negative" if value < 0
@radius = value
@area = 3.14159 * @radius ** 2
end
end
circle = Circle.new(5)
puts circle.radius
puts circle.area Ruby's
attr_reader :name, attr_writer :name, and attr_accessor :name generate getter/setter methods automatically. For simple cases, use attr_accessor instead of Python's @property boilerplate. For validated setters, define a method named name=(value) — Ruby routes obj.name = value to that method.Inheritance
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound"
class Cat(Animal):
def speak(self):
return f"{self.name} says Meow!"
def purr(self):
return f"{self.name} purrs..."
cat = Cat("Whiskers")
print(cat.speak())
print(cat.purr())
print(isinstance(cat, Animal)) # True class Animal
def initialize(name)
@name = name
end
def speak = "#{@name} makes a sound"
end
class Cat < Animal
def speak = "#{@name} says Meow!"
def purr = "#{@name} purrs..."
end
cat = Cat.new("Whiskers")
puts cat.speak
puts cat.purr
puts cat.is_a?(Animal) # true Ruby uses
< for inheritance (class Cat < Animal), Python uses parentheses (class Cat(Animal)). Both support single inheritance only at the class level; mixins provide Ruby's equivalent of Python's multiple inheritance. Override methods by redefining them; call the parent with super.Class and static methods
class Temperature:
def __init__(self, celsius):
self.celsius = celsius
@classmethod
def from_fahrenheit(cls, fahrenheit):
return cls((fahrenheit - 32) * 5 / 9)
@staticmethod
def absolute_zero():
return -273.15
temp = Temperature.from_fahrenheit(98.6)
print(f"{temp.celsius:.2f}")
print(Temperature.absolute_zero()) class Temperature
attr_reader :celsius
def initialize(celsius)
@celsius = celsius
end
def self.from_fahrenheit(fahrenheit)
new((fahrenheit - 32) * 5.0 / 9)
end
def self.absolute_zero = -273.15
end
temp = Temperature.from_fahrenheit(98.6)
puts "%.2f" % temp.celsius
puts Temperature.absolute_zero Ruby has no
@classmethod/@staticmethod distinction — both are def self.method_name. Inside a class method, new refers to the class itself (like Python's cls(...)). Class methods are called directly on the class: Temperature.from_fahrenheit(98.6).__str__ / __repr__ vs to_s / inspect
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"({self.x}, {self.y})"
def __repr__(self):
return f"Point({self.x!r}, {self.y!r})"
point = Point(3, 4)
print(str(point)) # (3, 4)
print(repr(point)) # Point(3, 4) class Point
attr_reader :x, :y
def initialize(x, y)
@x = x
@y = y
end
def to_s = "(#{@x}, #{@y})"
def inspect = "Point(#{@x.inspect}, #{@y.inspect})"
end
point = Point.new(3, 4)
puts point.to_s # (3, 4)
puts point.inspect # Point(3, 4)
p point # Point(3, 4) — p calls inspect Ruby's
to_s is Python's __str__, and inspect is Python's __repr__. puts obj calls to_s; p obj calls inspect. Overriding to_s also improves string interpolation output since #{obj} calls to_s.Modules & Mixins
import vs require
import math
import os
from pathlib import Path
from collections import defaultdict
print(math.pi)
print(math.sqrt(16)) require "set"
require "json"
# Standard library available without require in IRB/pry;
# in scripts, require what you use
puts Math::PI
puts Math.sqrt(16)
# require_relative for files relative to current file
# require_relative "./my_module" Ruby uses
require "library_name" (string, no path extension). Python uses import module_name. Ruby's core classes (String, Array, Hash, Integer) need no require. The standard library needs require for modules like "set", "json", "date". Use require_relative for your own files.Multiple inheritance vs include
class Flyable:
def fly(self):
return f"{self.__class__.__name__} is flying"
class Swimmable:
def swim(self):
return f"{self.__class__.__name__} is swimming"
class Duck(Flyable, Swimmable):
pass
duck = Duck()
print(duck.fly())
print(duck.swim()) module Flyable
def fly = "#{self.class.name} is flying"
end
module Swimmable
def swim = "#{self.class.name} is swimming"
end
class Duck
include Flyable
include Swimmable
end
duck = Duck.new
puts duck.fly
puts duck.swim
puts duck.is_a?(Flyable) # true Python achieves code reuse through multiple inheritance; Ruby uses
module and include (mixins). Ruby modules cannot be instantiated — they exist solely to be mixed into classes. include adds module methods as instance methods; extend adds them as class methods. A class can include any number of modules.Comparable mixin
# Python: define __lt__, __le__, etc., or use @functools.total_ordering
from functools import total_ordering
@total_ordering
class Temperature:
def __init__(self, celsius):
self.celsius = celsius
def __eq__(self, other):
return self.celsius == other.celsius
def __lt__(self, other):
return self.celsius < other.celsius
temps = [Temperature(30), Temperature(10), Temperature(20)]
print(sorted(temps, key=lambda t: t.celsius)[0].celsius) class Temperature
include Comparable
attr_reader :celsius
def initialize(celsius)
@celsius = celsius
end
# Only need to define <=>; Comparable provides all other operators
def <=>(other)
celsius <=> other.celsius
end
end
temps = [Temperature.new(30), Temperature.new(10), Temperature.new(20)]
puts temps.min.celsius # 10
puts temps.sort.map(&:celsius).inspect # [10, 20, 30] Ruby's
Comparable mixin is included with include Comparable. Implement the spaceship operator <=> (returning -1, 0, or 1) and you get <, <=, >, >=, between?, and clamp for free. Python's @functools.total_ordering achieves the same but requires defining two methods.Error Handling
try / except vs begin / rescue
try:
result = int("not a number")
print(result)
except ValueError as error:
print(f"Caught: {error}")
except (TypeError, RuntimeError) as error:
print(f"Other error: {error}") begin
result = Integer("not a number")
puts result
rescue ArgumentError => error
puts "Caught: #{error}"
rescue TypeError, RuntimeError => error
puts "Other error: #{error}"
end Ruby's
begin/rescue/end is equivalent to Python's try/except. The error variable is bound with => in Ruby (rescue Error => error) vs Python's as (except Error as error). Note Integer("x") raises ArgumentError, not ValueError — Ruby's exception hierarchy differs from Python's.finally vs ensure
try:
file = open("/tmp/pyrb-test-file.txt", "w")
file.write("data")
except IOError as error:
print(f"IO error: {error}")
else:
print("Write succeeded")
finally:
print("Always runs") begin
result = "simulated write"
puts "Write result: #{result}"
rescue IOError => error
puts "IO error: #{error}"
else
puts "Write succeeded"
ensure
puts "Always runs"
end Ruby's
ensure is equivalent to Python's finally — always executes regardless of exceptions. Ruby's else in a begin/rescue block runs only when no exception was raised — same as Python's else in a try block. Python's context managers (with open()) have no direct equivalent in Ruby, though File.open(path) { |f| ... } closes automatically.Custom exceptions
class InsufficientFundsError(Exception):
def __init__(self, amount, balance):
self.amount = amount
self.balance = balance
super().__init__(f"Cannot withdraw {amount}, balance is {balance}")
try:
raise InsufficientFundsError(100, 50)
except InsufficientFundsError as error:
print(error) class InsufficientFundsError < StandardError
attr_reader :amount, :balance
def initialize(amount, balance)
@amount = amount
@balance = balance
super("Cannot withdraw #{amount}, balance is #{balance}")
end
end
begin
raise InsufficientFundsError.new(100, 50)
rescue InsufficientFundsError => error
puts error
end Both languages define custom exceptions by inheriting from a base class. Python inherits from
Exception; Ruby inherits from StandardError (or a more specific subclass). super(message) passes the message to the base class. Ruby's exception hierarchy: Exception > StandardError > RuntimeError — rescue StandardError to catch all typical program errors.raise / re-raise
def validate_age(age):
if not isinstance(age, int):
raise TypeError(f"age must be int, got {type(age).__name__}")
if age < 0:
raise ValueError(f"age must be non-negative, got {age}")
return age
try:
try:
validate_age(-5)
except ValueError as error:
print(f"Caught: {error}")
raise # re-raise — bare raise preserves original traceback
except ValueError as error:
print(f"Propagated: {error}") def validate_age(age)
raise TypeError, "age must be Integer, got #{age.class}" unless age.is_a?(Integer)
raise ArgumentError, "age must be non-negative, got #{age}" if age < 0
age
end
begin
begin
validate_age(-5)
rescue ArgumentError => error
puts "Caught: #{error}"
raise # re-raise — bare raise preserves original traceback
end
rescue ArgumentError => error
puts "Propagated: #{error}"
end Ruby's
raise ExceptionClass, "message" is equivalent to Python's raise ExceptionClass("message"). A bare raise re-raises the current exception in both languages. Ruby's conventional exceptions for type errors and value errors are TypeError and ArgumentError respectively (Python uses TypeError and ValueError).Pattern Matching
match / case vs case / in
# Python 3.10+ structural pattern matching
command = {"action": "move", "direction": "north", "steps": 3}
match command:
case {"action": "move", "direction": direction, "steps": steps}:
print(f"Moving {direction} by {steps} steps")
case {"action": "stop"}:
print("Stopping")
case _:
print("Unknown command") command = { action: "move", direction: "north", steps: 3 }
case command
in { action: "move", direction: String => direction, steps: Integer => steps }
puts "Moving #{direction} by #{steps} steps"
in { action: "stop" }
puts "Stopping"
else
puts "Unknown command"
end Ruby's pattern matching uses
case/in (as opposed to the simpler case/when). Both Ruby and Python support structural pattern matching on hashes/dicts. Ruby's String => direction binds the matched value to a variable with type checking — similar to Python's direction: str() guard (though syntax differs).Sequence patterns
point = [3, 4]
match point:
case [0, 0]:
print("Origin")
case [x, 0]:
print(f"On x-axis at {x}")
case [0, y]:
print(f"On y-axis at {y}")
case [x, y]:
print(f"Point at ({x}, {y})") point = [3, 4]
case point
in [0, 0]
puts "Origin"
in [x, 0]
puts "On x-axis at #{x}"
in [0, y]
puts "On y-axis at #{y}"
in [x, y]
puts "Point at (#{x}, #{y})"
end Array/sequence patterns match almost identically in both languages. Variables in the pattern capture matched elements —
[x, y] destructures the array into x and y. Python calls these "sequence patterns"; Ruby calls them "array patterns". Both support wildcard (_) for ignored positions.Guard conditions
numbers = [1, 2, 3, 4, 5]
match numbers:
case [first, *rest] if first > 0:
print(f"Starts positive: {first}, rest={rest}")
case [first, *rest]:
print(f"Non-positive start: {first}")
case []:
print("Empty") numbers = [1, 2, 3, 4, 5]
case numbers
in [first, *rest] if first > 0
puts "Starts positive: #{first}, rest=#{rest.inspect}"
in [first, *rest]
puts "Non-positive start: #{first}"
in []
puts "Empty"
end Guard conditions use
if condition after the pattern in both languages — the pattern must match AND the guard must be true. The splat *rest captures remaining elements in both Python and Ruby. Ruby's pattern matching was added in Ruby 2.7 (finalized in 3.0); Python's match was added in 3.10.Deconstruct in custom classes
# Python: use __match_args__ for positional matching
class Point:
__match_args__ = ("x", "y")
def __init__(self, x, y):
self.x = x
self.y = y
point = Point(3, 4)
match point:
case Point(x=0, y=0):
print("Origin")
case Point(x=px, y=py):
print(f"Point({px}, {py})") class Point
attr_reader :x, :y
def initialize(x, y)
@x = x
@y = y
end
# deconstruct_keys enables hash-pattern matching
def deconstruct_keys(keys) = { x: @x, y: @y }
end
point = Point.new(3, 4)
case point
in { x: 0, y: 0 }
puts "Origin"
in { x: px, y: py }
puts "Point(#{px}, #{py})"
end Both languages allow custom classes to participate in structural pattern matching. Python uses
__match_args__ for positional matching. Ruby uses deconstruct_keys(keys) (for hash patterns) and deconstruct (for array patterns). Implement whichever your class's natural structure maps to.