F-string "debug" conversion

This is Larry Hasting’s idea, proposed at the core sprints earlier this month (it looks like Larry isn’t on Discourse yet, so I can’t @-mention him yet). I mentioned it to a few people there, but I haven’t had time to work on it yet. I’m posting it here mostly as a way to get experience with Discourse, but feel free to discuss it here. It really belongs on python-ideas. If we like it, I’ll eventually bring it up there.

Here’s the idea: we add a !d conversion operator, which is superficially similar to !s, !r, and !a. The meaning of !d is: produce the text of the expression (not its value!), followed by an equal sign, followed by the value of the expression. So:

value=10
print(f'{value!d}')

produces:

value=10

and:

print(f'next: {value+1!d}')

produces:

next: value+1=11

I’m not proposing this for str.format(). It would only really make sense for named arguments, and I don’t think print('{value!d}'.format(value=value) is much of a win.

The result is a string, so if you really wanted to, you could use a string formatting spec. So:

print(f'*{value!d:^20}*'

would produce:

*      value=10      *

Although I don’t think that would be very useful in general.

The mnemonic is !d for “debugging”. I’d wanted to use !=, because there’s an equal sign involved in the result, but = is the one character that can’t be used after ! (it’s “not equal” in expressions, and f-strings look specifically for that case). I also mentioned !!, but I think I prefer !d as being less confusing.

This would be used in debugging print statements, that currently end up looking like:

print(f'value={value}')

Thoughts?

My feeling is: I’m not sure it’s a great idea, but I’d use it every day if it existed.

7 Likes

This suggests it’s at least an intriguing idea. In fact, I’d use it, too.

The question then becomes, what problems are there with this? I can see the following:

  • This will forever bind the meaning of !d to be “debug”, and not “digit”, “dimension”, “dict”, and so on. So it would probably be helpful to survey what functionality this might eventually conflict with.
  • This is not very discoverable so plenty of users will still write f"value={value}". Could we help with that somehow? Could people confuse !d to mean something else?
  • Is the "expression=value" form always clear? Are there possible expressions where this would be ambiguous?

None of those concerns are major by the way. I think this is a worthwhile addition.

1 Like

That’s a good question. And it reminds me of something I forgot to mention: perhaps text_of_expr=repr(value_of_expr) is a more useful output? But I can see wanting both str() and repr(), which makes it more complicated: then you need either two conversion characters (like !d and !D, maybe), or have the result of !d not be a string but be some other object whose __format__() could then interpret its own format mini-language. Then you could do f'{value!d:r}' to mean use the repr of the value, or maybe allow a string other than = with something like f'{value!d:r->}. Of course, you have to decide if it’s worth the complexity of a new object, and how much you’d like its format mini-language to be compatible with str’s, if at all. It begins to ruin the simplicity of the concept, but maybe it’s worth it.

[edit for grammar]

2 Likes

Could it be a modifier to !s and !r?

I like the general idea very much,

2 Likes

Like !sd or !rd (or maybe !s= and !r=)? It could, but I’m concerned it would break other implementations that don’t expect more than a single character for the conversion specifier. But I could get over that objection.

Maybe !S and !R could be the “debug” versions. Then switching back and forth between debug and non-debug would involve changing the case of the character. Although that’s not much different from dropping the = in the conversion+modifier version.

I like the mnemonic of !d and I think it would be okay to use !d for str and !D for repr. It doesn’t need to be overthunk. You probably won’t see this much in production code, but it’ll be darn handy for development and I’d probably use it multiple times a day.

1 Like

By which I mean: for tools such as editors that don’t care what the conversion is and just skips over it (perhaps with a regex), such naive code might break if it assumes a single character. Again, I could get over this: people have to change code all the time due to language changes.

True enough. !d and !D are probably the simplest thing we could do.

OTOH, unless we say that !d/!D cannot be followed with a format spec (colon followed by something), then we would be either living forever with a str return type for !d/!D, or at least if we later decide we want a format spec that has extra functionality (like change ‘=’ to ‘:’), it would have to be a superset of the str format spec mini-language. Because you know someone is going to write f'*{value!d:^20}*'.

Maybe the best thing to do now is return an object from !d/!D that requires an empty string for the format spec argument to its __format__(). That would leave our options open in the future. And it’s not incompatible with using both !d and !D. I’ll admit I haven’t thought through all of the compile-time vs. run-time issues.

I propose there should only be !d and it should always use repr(). That’s simplest. If that’s not what people want they can spell it out the old way.

2 Likes

Makes sense. I think I’ll mention it on python-ideas and sit back and enjoy the show.

1 Like

Or we could make the modifiers chainable, e.g. f"{sys.byteorder!r!d}" gives "sys.byteorder='little'" and f"{sys.byteorder!s!d}" (or simply f"{sys.byteorder!d}") gives "sys.byteorder=little".

Would !d!r make sense? (First make the debug output (a string) then do a repr() on that, i.e. "'sys.byteorder=little'".)

This is such a minor feature, it’s not wort complicating anything else for it.

Apologies for commenting on an old thread, I would love to see this too and would make print style debugging lot easier. I would like to follow up on this idea if it has been posted on python-ideas. Rust introduced a similar feature in 1.32 and has received highly positive feedback.

fn main() {
    let x = 5;

    dbg!(x);
}

Running the above would give

[src/main.rs:4] x = 5

Edit : Found the discussion at https://mail.python.org/pipermail/python-ideas/2018-October/053956.html. My bad on searching

1 Like

FWIW: I have implemented this in a python package: github.com/tylerwince/pydbg

I would love to see this behavior or something similar make it into the stdlib to be a close cousin to breakpoint() for situations where full blown debugging doesn’t make sense or isn’t practical

3 Likes

I have this partially implemented. I’ll get to it, eventually. Hopefully before PyCon.

7 Likes

repr makes the most difference for strings with non-printable-ascii chars, where repr is needed to see what chars the string actually has

1 Like

This has turned into the feature request in https://bugs.python.org/issue36774.

bpo36744 was ultimately rejected in favor of https://bugs.python.org/issue36817, since applied.

1 Like