<sub>2026-01-31 @2100</sub> #python #ruby # Object Representation in Python & Ruby While porting a regex-heavy feature from Ruby to Python, I hit a bug that wasn't a bug. That pattern in the Ruby code and the ported version to Python matched exactly what i wanted. Yet the output looked different enough that I assumed the port was wrong. What i was comparing was not the data it was representations of that data between two different languages. Sounds subtle until it costs you debugging time. ### Printed Output Is a Representation, Not the Value Programs store data as structured values in memory. When you print something, the language converts that value into a string form so it can be displayed. That display form is a representation. In my own words, it's a formatting decision by the language layered on top of the value. Different representations can describe the same data. Two important categories pop up when describing formatted output. | Type | Purpose | | -------------------- | ----------------------------------------------------- | | Readable (ambiguous) | Easy for humans to read (may omit escapes details) | | Unambiguous | Precise enough for original value to be reconstructed | An unambiguous representation supports round-tripping which is to say we can go from a value to a representation and back. Most debugging views favor the unambiguous form. ### Where Python and Ruby Differ Both languages support two conceptual layers. The confusion begins when languages implicitly switch to the unambiguous form. For example, when printing container elements. | Intent | Python | Ruby | | ------------ | ---------------------- | --------------- | | Human-facing | `str(obj)` / `print()` | `to_s` / `puts` | | Unambiguous | `repr(obj)` | `inspect` / `p` | ### The Example That Triggered This Consider a regex extracting a quoted substring like the following. Both languages are extracting the same value `"hello"`. ``` "He said \"hello\"" ``` ##### Python ```Python import re text = 'He said "hello"' match = re.search(r'"hello"', text) result = match.group() print([result]) # ['"hello"'] ``` ##### Ruby ```Ruby text = 'He said "hello"' match = text.match(/"hello"/) result = match[0] p [result] # ["\"hello\""] ``` ### Why the Output Looks Different The string in memory is identical in both languages `"hello"` but each language must choose a valid string literal form to display it unambiguously inside the container. ##### Python's Rule Python's `repr()` can use either single or double quotes for string literals. If the string contains double quotes, Python can wrap the literal in single quotes to avoid escaping. `'"hello"'`. ##### Ruby's Rule Ruby's `inspect` always uses double quotes for string literals. Because the string itself contains double quotes, Ruby must escape them `"\"hello\""`. ### Why This Matters in Debugging Neither language changed the data. They simply chose different, valid ways to express the same string as a literal. You are comparing string literal syntax at this point not string contents. Regex debugging is especially prone to this illusion because patterns and matches are escape-heavy. When logs differ across languages, it's easy to assume: - The regex engine behaved differently - The string was altered underneath you - Escaping was incorrect to begin with The only difference ends up being how each language renders an unambiguous string representation. The way I like to word it is that the behavior of the program didn't change ***"the lens used to display the data did"***. Something to be aware of is all. > [!WARNING] > I've seen a great deal of mixing in usages of `p object` with `puts object.inspect` lately. Keep in mind the two are different in that they may produce the same visible output but they are not equivalent in what they return. > > ```Ruby > irb(main):001> text = "hello" > => "hello" > irb(main):002> p text > "hello" > => "hello" > irb(main):003> puts text.inspect > "hello" > => nil > ``` > > | Method | What it prints | What it returns | > | ------------------ | -------------- | --------------- | > | `p obj` | `obj.inspect` | returns `obj` | > | `puts obj.inspect` | same text | returns `nil` | > > So while they look interchangeable for logging, `p` preserves the value in an expression chain, and `puts` does not.