<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.