Comments on plain text in f-strings

Motivation

Comments are especially useful in multiline f-strings. There is no problem to insert them as part of the string, if it is supported downstream, like re module in verbose mode. But in other cases this can be either not possible or not readable (e.g no syntax highlighting).

Right now there is no documented way to add comments to f-strings. Python docs keep silence, PEP 498 says this is not possible.

(Added later) It is possible to comment on f-string expression, but not on a line of text:

f'''
foo = {
   1 + 2  # comment
}
...
text that needs a comment  { # syntax error
} 
'''

There are semi-solutions that abuse expressions syntax:

f'{'comment':.0}'
f'{'comment'[:0]}'
f'{'comment' and ''}'
f'{'comment' * 0}'

But there is no preferred or recommended way to do it. Having a documented solution will provide some benefits, for example, allow syntax highlighting in IDE plugins.

Possible solutions

Keeping aside introducing completely new syntax,
f'text {.comment}'
f'text {//comment}'
f'text {--comment}'
f'text {?comment}'
f'text {?:comment}' # similar to re

these could be:

  1. Document one of “syntax hacks” above — this will allow plugin developers to implement syntax highlighting. Optimizations may be added to not produce excessive bytecode in this case.
  2. Add new conversion !i or !x that will ignore the whole expression:
f'{'commment'!i}'

Do you think any of these is reasonable?

2 Likes

How about breaking an f-string into pieces so you can add comments outside of them?

s = (
    f'input = {x}, ' # comment about x
    f'output = {y}'  # comment about y
)
12 Likes

This is perfectly readable, but what about multiline portion of other language and IDE without plugin for inline syntax highlihting:

RULES = f'''
bterm_1p : BCHAR_1P | BRANGE_1P  {'1-st term in positive br exp':.0}
BCHAR_1P : /[^!]/
BRANGE_1P.1 : /[^!]-[^]]/

bterm_1n : BCHAR_1N | BRANGE_1N  {'1-st term in negative br exp':.0}
BCHAR_1N : /./
BRANGE_1N.1 : /.-[^]]/

bterm_k : BCHAR_K | BRANGE_K  {'K-th term':.0}
BCHAR_K : /[^]]/
BRANGE_K.1 : /[^]]-[^]]/
'''

(imagine there is no native comments syntax)

True that for a multiline string breaking it into pieces would make it uglier:

RULES = (
'''
bterm_1p : BCHAR_1P | BRANGE_1P''' # 1-st term in positive br exp
'''
BCHAR_1P : /[^!]/
BRANGE_1P.1 : /[^!]-[^]]/

bterm_1n : BCHAR_1N | BRANGE_1N''' # 1-st term in negative br exp
'''
BCHAR_1N : /./
BRANGE_1N.1 : /.-[^]]/
'''
)

The and 0 and * 0 hacks are fairly optimized already:

import dis

def f():
    return f'{'comment'*0}'

dis.dis(f)

outputs:

  3           RESUME                   0

  4           LOAD_CONST               1 ('')
              FORMAT_SIMPLE
              RETURN_VALUE

Having to first make the comment a string and then add a conversion specifier still makes it look as ugly as those hacks though:

f'''
bterm_1p : BCHAR_1P | BRANGE_1P {'comment'!i}
'''

I think a new syntax may be the better way to go if we are to add this feature. Perhaps we can borrow Jinja2’s comment syntax, which I find elegant:

f'''
bterm_1p : BCHAR_1P | BRANGE_1P {# comment #}
'''
3 Likes

It’s not obvious to me that the intention of the { :.0}s are comments, and that I should read them.

I would refactor the multiline string to use Ben’s elegant suggestion.

1 Like

Why are they any more useful in multiline f-strings than in normal multiline strings?

If the expressions you’re interprolating are complex, extract those into standalone variables calculated before the f-string, and comment the calculation just like any normal Python expression (which it is).

7 Likes

multi-line f-strings already support embedded comments (IIRC this was introduced in 3.12):

>>> f"hello {
 ... 1+2 # a calculation
 ... }"
'hello 3'
6 Likes

Yes, but in this case you are commenting expression, not the string text itself. Using it without expression does not work (syntax error):

f'''
some text { # comment }
'''
f'''
some tex {
   # comment
}
'''

The difference is that for normal multiline strings it is really impossible, and for f-strings there is a formatting syntax that gives some flexibility.

New variable names will be added when they are not needed. Such an artificial separation, when it was only needed to add some notes to string’s content, looks conceptually wrong to me.

Do you think there is no need in inline comments in multiline f-strings?

Yes, I do.

6 Likes

+1 on the f"{'my comment' * 0}"

1 Like

Why would you want to use comments without an expression? That’s an honest question, I’d expect that the comments are primairily useful to explain complex expressions in the f-string.

BTW. A work around for the need to have an expression is to use one that evaluates to an empty string, such as… an empty string:

f"{'' # some comment
}"

The newline is necessary because Python comments run to the end of a line, using slightly different rules inside f-strings would IMHO lead to additional semantic complexity and introduces more cases where it is no longer possible to copy and expression with a comment into an f-string.

4 Likes

Consider this case, or any other case when one wants to maintain a piece of text/code (SQL, grammar, XML, whatever) embedded in python file. There may be some expressions in this multiline string, as well as complex places where additional comments are needed.

The example above could be broken into 3 separate strings, but what if it is a piece of XML or a highly nested SQL query, where breaking will harm readability? For SQL and XML I could add native inline comments, but for a format not supporting comments the only option would be breaking or using “hacks”. I’m not against hacks in this rare case, but I’d like to have syntax highlighting.

If not introducing completely new syntax for f-string comments, having an ignoring formatting conversion like !i does not seem a complex task (I could even try it myself) and won’t add complexity to Python grammar.

For something more readable than “syntax hacks” like f"{'comment'*0}", you could define a function:

def comment(*args: Any) -> Literal[""]:
    return ""

which can be used as:

RULES = f"""
bterm_1p : BCHAR_1P | BRANGE_1P  {comment('1-st term in positive br exp')}
BCHAR_1P : /[^!]/
BRANGE_1P.1 : /[^!]-[^]]/

bterm_1n : BCHAR_1N | BRANGE_1N  {comment('1-st term in negative br exp')}
BCHAR_1N : /./
BRANGE_1N.1 : /.-[^]]/

bterm_k : BCHAR_K | BRANGE_K  {comment('K-th term')}
BCHAR_K : /[^]]/
BRANGE_K.1 : /[^]]-[^]]/
"""

Granted, this won’t be optimized away like some syntax hacks, but I can’t imagine the speed difference being noticeable, especially if this is done only once at import time.

1 Like

On one hand the expression is complicated enough to warrant a comment.
On the other hand the expression is not complicated enough to warrant a name.
That is quite a combination.

Perhaps if you take the time to come up with a good name, you will not need the comment.

2 Likes

Yes, this would be true if it was about commenting on expression, but this post is about commenting on a line of plain text in a string.

1 Like

Apologies for misunderstanding, I scanned the thread too hastily.

1 Like

And hence my point. Why are f-strings different? The use case for commenting on a line of text in any multi-line string is the same.

3 Likes

I agree, being able to comment on line of normal string is the same use case. But is it possible?

1 Like

Generally, use the comment capabilities in whatever language you’re embedding in a multi-line string. For example, in SQL

sql = """\
SELECT
    name, salary -- the fields I need
FROM
    emp          -- the employee table
"""

If you aren’t embedding a well-known language, I don’t understand your point about syntax highlighting. But it’s pretty easy to add post-processing to strip lines starting with (say) #:

>>> a = """\
... one
... # Some junk
... two
... """
>>> print("".join(l for l in a.splitlines(keepends=True) if not l.startswith("#")))
one
two

>>>

I don’t think you’ve provided a compelling argument as to why we need an additional comment syntax, especially not if it only works in f-strings and not in all multi-line strings. “It’s possible” isn’t an argument for doing something.

6 Likes