puts adds a newline, print does not, p shows inspect¶
Level: 101 · anyone whose fingers type print(...) and want to know which of Ruby's four to reach for
One line: puts writes to_s plus a newline it does not double, flattening arrays, printing nil as an empty line and an empty array as nothing; print writes to_s and nothing else; p writes inspect and returns its argument; pp is p that wraps at 80 columns — where Python has one print with sep, end and file keywords, and repr/pprint for the rest.
All four are Kernel methods that write to $stdout, which is why the program below can swap $stdout for a StringIO, run each call, and show the exact characters it wrote. puts calls to_s on each argument and adds a newline only when the string does not already end in one; an array is flattened and written one element per line, nil and an empty array become an empty line, and it returns nil. print calls to_s and adds nothing. p calls inspect — the debugging view, quotes and all — and returns what it was given (one object, or an array of several), which makes p safe to wrap around any expression in a chain. pp is p with line-wrapping at the terminal's width, or 80 columns when the output is not a terminal — it reads COLUMNS in that case, which the program clears so the run is the same everywhere. $stdout.write is the primitive under them all and returns the number of bytes it wrote; printf is format plus write; << chains. warn and $stderr.puts write to $stderr, the stream the library's runner does not record, so the program captures it the same way.
Verified output of puts_print_p_and_pp_rb.rb — regenerated by tools/run_examples.py, never hand-typed.
1. puts "a" writes "a\n"
2. puts "a\n" adds no second newline "a\n"
3. puts 1, 2 puts each argument on its own line "1\n2\n"
4. puts [1, [2, 3]] flattens, one element per line "1\n2\n3\n"
5. puts nil writes an empty line "\n"
6. puts [] writes nothing at all ""
7. puts with no argument "\n"
8. print "a", "b" adds no separator and no newline "ab"
9. print nil, 1.0 uses to_s, and nil.to_s is empty "1.0"
10. p "s" writes inspect, so the quotes are visible "\"s\"\n"
11. p 1, 2 writes two lines "1\n2\n"
12. and returns its arguments as an Array [1, 2]
13. p with one argument returns it; p alone returns "s"; nil
14. puts returns nil
15. puts obj uses to_s; p obj uses inspect "Foo-to_s\n"; "Foo-inspect\n"
16. "#{obj}" uses to_s; puts [obj] uses to_s of each "Foo-to_s\n"; "Foo-to_s\n"
17. p [obj] uses inspect of each element "[Foo-inspect]\n"
18. p h writes one line, this long 175
19. pp h wraps at 80 columns, keeping insertion order:
{name: "Ada Lovelace",
languages: ["ruby", "python", "perl"],
address: {city: "London", country: "United Kingdom"},
notes: "a fairly long string to push past eighty columns"}
20. pp's first line {name: "Ada Lovelace",
21. pp returns its argument (equal?) true
22. $stdout.write("caf\u{e9}\n") returns bytes written 6
23. $stdout.write("ab", "cd\n") takes several arguments 5
24. printf("%05.1f|%s\n", 3.14159, "x") formats, writes "003.1|x\n"
25. $stdout << "a" << 1 << "\n" chains "a1\n"
26. $stderr.puts and warn both write to $stderr "to stderr\nwarned\ntwice\n"
27. warn takes several arguments, one per line 3
Reading the output¶
Rows 1–7 are puts: a newline added (row 1) but not doubled (row 2), each argument on its own line (row 3), arrays flattened (row 4), nil and no argument at all each producing one empty line (rows 5 and 7) — while an empty array writes nothing at all, not even a newline (row 6). Rows 8–9 are print: no separator, no newline, and nil.to_s is the empty string, so print nil, 1.0 writes just 1.0. Rows 10–14 are p: it writes inspect, so a string keeps its quotes (row 10); several arguments become several lines (row 11) and the return value is the array of them (row 12); one argument comes back as itself and a bare p returns nil (row 13); puts always returns nil (row 14). Rows 15–17 fix the rule: puts, print and "#{}" interpolation use to_s, p uses inspect — and puts [obj] flattens, so it still uses to_s of the element, while p [obj] shows Array#inspect, which calls inspect on each element. Rows 18–21 are pp: p h is one line of 175 characters with its newline, pp h wraps it at 80 columns and keeps the Hash's insertion order (name first), and it returns the same object. Rows 22–25 are the primitives: write counts bytes, so the six bytes of "café\n" return 6; it accepts several arguments; printf formats then writes; << returns the stream so it chains. Rows 26–27: warn and $stderr.puts both landed in the swapped-in $stderr, and warn with two arguments wrote two lines, as puts would.
Compared with Python¶
Python has one function. print(*args, sep=" ", end="\n", file=sys.stdout, flush=False) writes str() of each argument joined by sep and followed by end, so print(1, 2) stays on one line where puts 1, 2 makes two (row 3), print([1, [2, 3]]) writes the list's repr where puts flattens (row 4), and print(None) writes the word None where puts nil writes an empty line (row 5). print("a\n") adds its end regardless, giving a blank line, where puts checks for one first (row 2). Ruby's print is print(..., sep="", end="") (row 8). There is no p: the spelling is print(repr(x)), and print returns None whatever it printed (rows 10–14) — so the "wrap it in p and look" trick becomes print(repr(x)); x or an f-string with !r. The to_s/inspect pair is __str__/__repr__ (rows 15–17), with the same rule that containers use repr of their elements — the Python library's repr is not str ↗ measures it. pprint.pprint wraps at width=80 but sorts dict keys by default (row 20 shows address first); pass sort_dicts=False for insertion order (row 19), and it returns None (row 21). sys.stdout.write returns the number of characters — 5 for "café\n" where Ruby returned 6 bytes (row 22) — takes one string (row 23), and cannot chain (row 25). Standard error is print(..., file=sys.stderr), and several arguments stay on one line there too (rows 26–27); Standard in, standard out, and pipes ↗ is the Python library's page on the three streams.
Verified output of puts_print_p_and_pp_py.py — regenerated by tools/run_examples.py, never hand-typed.
1. print("a") writes 'a\n'
2. print("a\n") adds its newline anyway 'a\n\n'
3. print(1, 2) joins with sep=' ' on one line '1 2\n'
4. print([1, [2, 3]]) writes the list's repr '[1, [2, 3]]\n'
5. print(None) writes the word None 'None\n'
6. print([]) writes [] '[]\n'
7. print() with no argument '\n'
8. print("a", "b", sep="", end="") is Ruby's print 'ab'
9. print(None, 1.0, end='') uses str, None included 'None 1.0'
10. print(repr("s")) is the p spelling "'s'\n"
11. print(repr(1), repr(2)) stays on one line '1 2\n'
12. print returns None
13. there is no p; repr(x) returns the string "'s'"
14. print returns None
15. print(obj) uses str; print(repr(obj)) uses repr 'Foo-str\n'; 'Foo-repr\n'
16. f"{obj}" uses str; print([obj]) uses repr of each 'Foo-str\n'; '[Foo-repr]\n'
17. print([obj]) uses repr of each element '[Foo-repr]\n'
18. print(h) writes one line, this long 187
19. pprint(h, sort_dicts=False) wraps at 80, in order:
{'name': 'Ada Lovelace',
'languages': ['ruby', 'python', 'perl'],
'address': {'city': 'London', 'country': 'United Kingdom'},
'notes': 'a fairly long string to push past eighty columns'}
20. pprint(h) default sort_dicts=True: first line {'address': {'city': 'London', 'country': 'United Kingdom'},
21. pprint returns None
22. sys.stdout.write('caf\N{...}\n') returns characters 5
23. sys.stdout.write takes one string, not several 5
24. print("%05.1f|%s" % (3.14159, "x")) is format + write '003.1|x\n'
25. no << on streams: write returns an int, no chain n/a
26. print(file=sys.stderr) is the spelling of both 'to stderr\nwarned twice\n'
27. several arguments stay on one line, like print 2
Try it¶
- Reimplement
putsasmy_puts(*args)on top of$stdout.write: flatten arrays, one item per line,nilas an empty line, no doubled newline, and an empty line for no arguments. Capture both yours and the realputsinto aStringIOfor eight inputs and print whether they agree. - Write a
Teeclass withwrite(*strings)that copies everything to two IO objects, assign an instance to$stdout, and show thatputs,printandpall reach both — then restore$stdoutin anensure.
Solution to 1
puts_print_p_and_pp_kata_rb.rb in full — pasted here by tools/run_examples.py from the file CI runs.
# Kata: reimplement puts on top of $stdout.write -- flatten arrays, one item per
# line, nil becomes an empty line, and a string that already ends in a newline
# gets no second one -- then check it against the real puts on several inputs.
require "stringio"
def my_puts(*args)
args = [""] if args.empty?
args.flatten.each do |item|
text = item.nil? ? "" : item.to_s
text += "\n" unless text.end_with?("\n")
$stdout.write(text)
end
nil
end
def capture
saved, $stdout = $stdout, StringIO.new
yield
$stdout.string
ensure
$stdout = saved
end
inputs = [["a"], ["a\n"], [1, 2], [[1, [2, 3]]], [nil], [[]], [], [:sym, 1.5]]
inputs.each do |args|
mine = capture { my_puts(*args) }
real = capture { puts(*args) }
puts format("%-16s mine=%-16s real=%-16s same=%s", args.inspect, mine.inspect, real.inspect, mine == real)
end
Verified output of puts_print_p_and_pp_kata_rb.rb — regenerated by tools/run_examples.py, never hand-typed.
["a"] mine="a\n" real="a\n" same=true
["a\n"] mine="a\n" real="a\n" same=true
[1, 2] mine="1\n2\n" real="1\n2\n" same=true
[[1, [2, 3]]] mine="1\n2\n3\n" real="1\n2\n3\n" same=true
[nil] mine="\n" real="\n" same=true
[[]] mine="" real="" same=true
[] mine="\n" real="\n" same=true
[:sym, 1.5] mine="sym\n1.5\n" real="sym\n1.5\n" same=true
See also¶
#{}callsto_s;formatis printf — the format stringsprintfandformatshareFile.openwith a block closes the file for you —f.putsandf.printon a file are these same methods on another IOgetsreturns nil at EOF, and readsARGV's files first — the input side of the three standard streamsto_s,inspectandp— definingto_sandinspecton your own class, and whatpreturnswarn "msg"goes to stderr, and-wreports what it sees —warnbeyond the two rows hererepris notstr↗ — Python's__str__/__repr__pair, measured- Standard in, standard out, and pipes ↗ —
sys.stdout,sys.stderrand what a pipe changes irbechoes every value; a script does not —irbis a read–eval–print loop: each line's value is printed withinspect,_holds the last one, an…- There is more than one way, and one obvious way — Ruby's idioms