puts calls to_s, p calls inspect¶
Level: 201 · anyone who has seen #<Foo:0x000...> and wanted a better line
One line: Every object answers two questions — to_s, "show this to a person", and inspect, "show this to a programmer" — and every printing route picks one: puts, interpolation, %s and String() use to_s, while p, %p, pp and every container's to_s use inspect; defining one never defines the other, p returns what it printed and puts returns nil, and puts nil writes an empty line.
The default to_s is #<Plain:0x…> with the object's address; the default inspect is the same plus the instance variables, which is the line you see from p. Since the address changes from run to run, the program tests the shape of those defaults instead of printing them, and prints the real strings only for classes that define their own. Both methods live in Kernel, so BasicObject has neither — a proxy built on BasicObject cannot even be p'd.
Verified output of to_s_inspect_and_p_rb.rb — regenerated by tools/run_examples.py, never hand-typed.
1. the two defaults
Plain.new.to_s starts with "#<Plain:0x" true
Plain.new.inspect == Plain.new.to_s (shape) true
Point.new(1, 2).inspect ends " @x=1, @y=2>" true
Point.new(1, 2).to_s includes "@x" false
2. defining one never defines the other
OnlyToS.new.to_s only to_s
OnlyToS.new.inspect is still the default true
OnlyInspect.new.inspect <OnlyInspect>
OnlyInspect.new.to_s is still the default true
3. who calls which
puts obj to_s of 3
p obj #<Both 3>
"#{obj}" to_s of 3
format("%s / %p", obj, obj) to_s of 3 / #<Both 3>
String(obj) to_s of 3
obj.to_s to_s of 3
obj.inspect #<Both 3>
4. containers inspect their elements, even in to_s
[obj].to_s [#<Both 3>]
[obj].inspect [#<Both 3>]
{ k: obj }.to_s {k: #<Both 3>}
"#{[obj]}" [#<Both 3>]
puts [obj] to_s of 3
5. p returns its argument, puts returns nil
p(obj) prints: #<Both 3>
...and returns obj itself true
p(1, 2) prints two lines:
1
2
...and returns [1, 2]
p() returns nil
puts("one") prints: one
...and returns nil
6. nil
puts nil writes "\n"
p nil writes "nil\n"
print nil writes ""
"#{nil}".inspect ""
nil.to_s.inspect ""
nil.inspect nil
[nil].to_s [nil]
7. strings and symbols
"str".to_s str
"str".inspect "str"
"tab\there".inspect "tab\there"
"\u{e9}".inspect "é"
:sym.to_s sym
:sym.inspect :sym
8. core classes define both
S.new(1, 2).inspect #<struct S x=1, y=2>
S.new(1, 2).to_s == its inspect true
D.new(1).inspect #<data D a=1>
RuntimeError.new("boom").to_s boom
RuntimeError.new("boom").inspect #<RuntimeError: boom>
1e20.to_s 1.0e+20
(1..3).inspect 1..3
9. pp wraps a nested structure at a width
{name: "widget",
parts:
[{id: 1,
tags: ["alpha", "beta", "gamma"]},
{id: 2,
tags: ["alpha", "beta", "gamma"]},
{id: 3,
tags: ["alpha", "beta", "gamma"]}]}
obj.pretty_inspect == obj.inspect + "\n" true
10. where they live
Object.instance_method(:to_s).owner Kernel
Object.instance_method(:inspect).owner Kernel
Kernel.private_instance_methods.include?(:p) true
BasicObject.instance_methods.include?(:inspect) false
Reading the output¶
- 1. A bare class gets a
to_sof#<Plain:0x…>, and itsinspectis the same string; give the object instance variables and onlyinspectgrows to list them (@x=1, @y=2), whileto_sstays as it was. - 2. Defining
to_sleavesinspectat the default and defininginspectleavesto_sat the default. There is no fall-back in either direction: a class that wants both must write both. - 3.
puts,"#{}",%sandString()all end up into_s;pand%pend up ininspect. - 4. A container's
to_sis itsinspect, and it inspects its elements —[obj].to_sshows#<Both 3>, notto_s of 3— so interpolating an array into a string gives the programmer's view of each element.puts [obj]is the exception:putsflattens an array and callsto_son each element separately. - 5.
p objprints and then returnsobjitself, which is whypcan be dropped into the middle of an expression to look at a value;p 1, 2prints two lines and returns the array[1, 2];pwith nothing returnsnil;putsalways returnsnil. - 6.
puts nilwrites a bare newline,p nilwritesnil, andprint nilwrites nothing — becausenil.to_sis""andnil.inspectis"nil". The same applies inside interpolation:"#{nil}"is the empty string. - 7. A string's
to_sis itself; itsinspectadds the quotes and escapes a tab as\t— but leaveséas is, since the program runs under-E UTF-8. A symbol'sinspectkeeps the colon and itsto_sdrops it. - 8.
Struct,Data, exceptions, floats and ranges all define both: a Struct'sto_sis itsinspect, an exception'sto_sis its message and itsinspectwraps the class around it, and1e20.to_sis1.0e+20. - 9.
PP.pp(object, $stdout, 40)isppwith an explicit width: it breaks the nested hash across lines and indents each level. Plainpppicks its width from the terminal, so the program pins it. (Not machine-checked here.) For an object whoseinspectis short,pretty_inspectis justinspectplus a newline. - 10.
to_sandinspectare owned byKernel, notObject;pis a private instance method ofKernel— a method every object has and calls without a receiver; andBasicObjecthas noinspectat all.
Compared with Python¶
Python's two questions are __str__ and __repr__, and the same routes lead to them: print, f"{x}", %s and str() call __str__; repr(), f"{x!r}", %r and every container call __repr__. Four rows differ. Row 1: Python's default __repr__ is <__main__.Point object at 0x…> with no attributes — where Ruby's default inspect lists the instance variables, Python makes you ask vars(). Row 2: defining __repr__ alone does give you str(), because object.__str__ falls back to __repr__; Ruby's to_s never falls back to inspect. Row 4: print([obj]) prints the list's repr, elements included — the same as Ruby's [obj].to_s — but Ruby's puts [obj] flattens the array and prints each element's to_s, which Python's print never does. Row 6: print(None) writes None, where puts nil writes an empty line. Python has no p: print always returns None, so the program defines a four-line p helper that prints the repr and returns its argument. It also has no Symbol; an Enum member is the nearest thing, with a str of Sym.sym and a repr of <Sym.sym: 1>. Dataclasses and namedtuples write a __repr__ for you the way Struct and Data write an inspect, pprint.pprint(width=40) is PP.pp with a width, and pformat of a short object is its repr. The Python library's repr is not str ↗ goes further into what repr escapes and why str.isprintable is defined in terms of it.
Verified output of to_s_inspect_and_p_py.py — regenerated by tools/run_examples.py, never hand-typed.
1. the two defaults
str(Plain()) starts with "<__main__.Plain object at 0x" True
repr(Plain()) == str(Plain()) True
repr(Point(1, 2)) mentions x True
vars(Point(1, 2)) {'x': 1, 'y': 2}
2. __str__ never defines __repr__; __repr__ does define str
str(OnlyStr()) only __str__
repr(OnlyStr()) is still the default True
repr(OnlyRepr()) <OnlyRepr>
str(OnlyRepr()) (falls back to __repr__) <OnlyRepr>
3. who calls which
print(obj) str of 3
p(obj) <Both 3>
f"{obj}" str of 3
"%s / %r" % (obj, obj) str of 3 / <Both 3>
str(obj) str of 3
f"{obj!s}" str of 3
f"{obj!r}" <Both 3>
4. containers repr their elements, even in str
str([obj]) [<Both 3>]
repr([obj]) [<Both 3>]
str({"k": obj}) {'k': <Both 3>}
f"{[obj]}" [<Both 3>]
print([obj]) [<Both 3>]
5. print returns None; the p helper returns its argument
p(obj) prints: <Both 3>
...and returns obj itself True
p(1, 2) prints two lines:
1
2
...and returns [1, 2]
p() returns None
print("one") prints: one
...and returns None
6. None
print(None) writes 'None\n'
p(None) writes 'None\n'
print(None, end='') writes 'None'
repr(f"{None}") 'None'
repr(str(None)) 'None'
repr(None) None
str([None]) [None]
7. strings; no symbol, so an Enum member
str("str") str
repr("str") 'str'
repr("tab\there") 'tab\there'
repr("\N{LATIN SMALL LETTER E WITH ACUTE}") 'é'
str(Sym.sym) Sym.sym
repr(Sym.sym) <Sym.sym: 1>
8. stdlib classes define both
repr(S(1, 2)) S(x=1, y=2)
str(S(1, 2)) == repr(S(1, 2)) True
repr(NT(1)) NT(a=1)
str(RuntimeError("boom")) boom
repr(RuntimeError("boom")) RuntimeError('boom')
str(1e20) 1e+20
repr(range(1, 4)) range(1, 4)
9. pprint wraps a nested structure at a width
{'name': 'widget',
'parts': [{'id': 1,
'tags': ['alpha',
'beta',
'gamma']},
{'id': 2,
'tags': ['alpha',
'beta',
'gamma']},
{'id': 3,
'tags': ['alpha',
'beta',
'gamma']}]}
pprint.pformat(obj) == repr(obj) True
10. where they live
Plain.__str__ is object.__str__ True
Plain.__repr__ is object.__repr__ True
hasattr(builtins, 'print') True
hasattr(object, '__repr__') True
Try it¶
- Write a
Versionclass built from"1.2.3"whoseto_sis the dotted string and whoseinspectis#<Version 1.2.3>. Print one withputs,p, interpolation, inside an array, and throughformat("%s / %p"); usep's return value in an expression; then includeComparablewith a<=>on the numeric parts and sort["1.10.0", "1.2.3", "1.2.10"]correctly. - Write a module
Inspectablewhoseinspectlists the class name and every instance variable (instance_variablesandinstance_variable_get) without an address, and include it in two different classes. Check thatpand[obj].to_sboth use it and thatputs objstill does not.
Solution to 1
to_s_inspect_and_p_kata_rb.rb in full — pasted here by tools/run_examples.py from the file CI runs.
# Exercise 1: a Version with a to_s for people and an inspect for programmers.
# Print it every way a value gets printed, and show that p hands it back.
class Version
include Comparable
attr_reader :parts
def initialize(text) = @parts = text.split(".").map(&:to_i)
def <=>(other) = parts <=> other.parts
def to_s = parts.join(".")
def inspect = "#<Version #{self}>"
end
v = Version.new("1.2.3")
print "puts v -> "
puts v
print "p v -> "
p v
puts "\"\#{v}\" -> #{v}"
puts "[v].to_s -> #{[v]}"
puts "format %s / %p -> #{format('%s / %p', v, v)}"
puts "p returns it -> #{p(v).to_s}"
puts "sorted -> #{[Version.new('1.10.0'), v, Version.new('1.2.10')].sort.inspect}"
Verified output of to_s_inspect_and_p_kata_rb.rb — regenerated by tools/run_examples.py, never hand-typed.
See also¶
newisallocatetheninitialize— the previous lesson; its classes all defineinspectfor the same reasonObjectsits onKernelonBasicObject— whereto_s,inspectandpare defined, and the object that has none of themputs,print,pandpp— the printing methods themselves: newlines, flattening, return values,ppwidth- String formatting and interpolation —
%s,%pand"#{}"in full - Struct and Data — the classes whose
inspectcomes for free - Custom exception classes —
message,to_sandinspecton an exception repris notstr↗ — the Python library's page on the two questions- Ruby docs:
Kernel#p↗ — andputs,printandppbeside it - Ruby docs:
PP↗ — pretty printing,pretty_inspectand the width argument - Python docs:
__repr__and__str__↗ — the data model's account, including the fall-back - Instance variables are private — An
@variablebelongs to one object and can be read or written only by that object's own methods