Ruby has block comments and a data section¶
Level: 101 · anyone reading a Ruby file that ends in __END__
One line: Ruby comments with #, with =begin/=end blocks that must start at column 0, and with magic comments the parser obeys; after __END__ the rest of the file is data the program reads through DATA — while Python has # alone, keeps a bare triple-quoted string as a real expression statement, and documents a function in a docstring the program can read back, which Ruby cannot.
A # comments to the end of the line in both languages, and a #! shebang on line one is a comment in both. From there they part. Ruby has a block comment, =begin … =end, with the odd rule that both words must sit at column 0 — an indented =begin is a syntax error. Ruby has magic comments on the first lines that change how the file is parsed: # frozen_string_literal: true freezes its string literals and # encoding: iso-8859-1 sets the source encoding (Python has the second, as # coding: latin-1, and nothing like the first). And Ruby has __END__: everything after it is not code, and the constant DATA is a File positioned at the first line after the marker — a habit inherited from Perl, and a neat way to keep a template or a fixture inside a script. What Ruby lacks is a docstring: documentation is a comment above the def, read by RDoc and YARD from the source, and a running method cannot be asked for it. The program measures each of those on this file and on small child files.
Verified output of comments_and_documentation_rb.rb — regenerated by tools/run_examples.py, never hand-typed.
1. x = 1 # trailing comment x = 1
2. =begin ... =end around x = 2 x = 1 (still 1: the block did not run)
3. an indented =begin (not at column 0) exit 1, syntax errors found (SyntaxError)
4. # frozen_string_literal: true / no comment true / false
5. # encoding: iso-8859-1, then 'caf' + byte E9 ISO-8859-1 / 4 / 4 (__ENCODING__ / length / bytesize)
6. __END__ then DATA.read "line one\nline two\n"
7. #!/usr/bin/env ruby as line 1 the shebang line is a comment
8. method(:documented).respond_to?(:__doc__) false
9. # @param line above def double; double(4) 8 (a YARD tag is an ordinary comment)
Reading the output¶
- Row 1: the code before a trailing
#ran. - Row 2:
x = 2sits inside=begin/=endin this very file, andxis still1. - Row 3: the same block indented by two spaces is a syntax error in a child Ruby — the child exited 1 and the parser's message is shown.
- Row 4: the magic comment made
'lit'frozen; the same literal in a file without it is not. - Row 5: with
# encoding: iso-8859-1,__ENCODING__reports that encoding and the literal'caf'plus the single byteE9is four characters and four bytes — that byte is one character in Latin-1. - Row 6:
DATA.readreturned the two lines after__END__at the bottom of the program file — text that was never parsed as Ruby. - Row 7: a shebang first line is a comment; the child printed its second line.
- Row 8: a
Methodhas no__doc__; the documentation is the comment abovedef documented, and only a tool reading the source sees it. - Row 9: a YARD
@paramtag is an ordinary comment; the method it documents behaves as it would without it.
Compared with Python¶
Python has one comment syntax. The common belief that a triple-quoted string is a block comment is measured in row 2: it is an Expr statement holding a Constant, evaluated and discarded, which is harmless at module level and surprising inside an expression. Rows 3 and 6 are the two Ruby markers fed to a Python child: =begin and __END__ are both a SyntaxError, because Python has no block comment and no data section — the idiom is a module-level string or a file beside the script. Row 4 has nothing to freeze: str has no __setitem__. Row 5 is the one magic comment the languages share: # coding: latin-1 makes the byte e9 one character (four characters, four bytes), and without it a non-UTF-8 source file fails to compile. Row 7 is identical. Rows 8–9 are the reverse of Ruby's: the docstring is the first statement of the function and the program reads it back through __doc__, while a # type: comment is an ordinary comment that leaves __annotations__ empty.
Verified output of comments_and_documentation_py.py — regenerated by tools/run_examples.py, never hand-typed.
1. x = 1 # trailing comment x = 1
2. """ ... """ around x = 2 x = 1 (still 1: the string is an Expr statement, its value a Constant)
3. =begin ... =end in a Python file exit 1, SyntaxError
4. no frozen-literal comment: str is immutable hasattr(str, '__setitem__') is False
5. # coding: latin-1, then 'caf' + byte e9 / without it 4 / 4 (length / bytes) / exit 1, SyntaxError
6. __END__ in a Python file exit 1, SyntaxError (no data section: use a string or a file)
7. #!/usr/bin/env python3 as line 1 the shebang line is a comment
8. documented.__doc__ 'Return a unchanged; this docstring is the documentation.'
9. # type: comment above; double(4), __annotations__ 8 {} (a type comment is an ordinary comment)
RDoc ships with Ruby and reads the # comment above each definition; YARD adds @param and @return tags to that comment; ri String#upcase shows what RDoc collected. PEP 257 is the docstring convention Python tools read. (Not machine-checked here.)
The Python library's string literals ↗ page covers the triple-quoted and raw forms that row 2's bare string uses.
Try it¶
- Put a small CSV table — a header line and three rows — after
__END__in a script, read it back throughDATA, and print it as an aligned table with a dashed line under the header and a count of the rows read. - Write a "doc by convention" reader: given a
Methodobject, usesource_locationto open its file, walk upwards from thedefline collecting the#comment lines directly above it, and print them. Test it on two methods, one documented and one not.
Solution to 1
comments_and_documentation_kata_rb.rb in full — pasted here by tools/run_examples.py from the file CI runs.
# Kata: the data section as a tiny table. Everything after __END__ is read
# back through DATA, one line at a time, and never parsed as Ruby.
rows = DATA.each_line(chomp: true).reject(&:empty?).map { |line| line.split(",") }
header, *body = rows
widths = header.each_index.map { |i| rows.map { |r| r[i].length }.max }
line = ->(cells) { cells.each_with_index.map { |c, i| c.ljust(widths[i]) }.join(" ") }
puts line.call(header)
puts widths.map { |w| "-" * w }.join(" ")
body.each { |cells| puts line.call(cells) }
puts "#{body.size} rows read from the data section"
__END__
name,language,year
Matz,Ruby,1995
Guido,Python,1991
Larry,Perl,1987
Verified output of comments_and_documentation_kata_rb.rb — regenerated by tools/run_examples.py, never hand-typed.
See also¶
- No docstrings — the comment-above-the-
defreader of exercise 2, built out - Indentation is convention in Ruby, syntax in Python — the frozen-literal magic comment as a style rule, and
-w - stdin,
getsandARGF—DATAis aFile; the other files a script reads without opening one - String literals are chilled, not frozen ↗ — the Ruby text library on what
# frozen_string_literal: truechanges - Ruby syntax: comments ↗ — the reference for
=begin, magic comments and__END__