F-string, maintainability vs pep8

Inside a class method I want to log a few values. The obvious way to do this is to use f-string

f"{(self.method1(), self.variable2, self.some_dict[‘some_key’], …) = }"

but that easily runs afoul of PEP8’s character limit on a line. Is there a recommended solution? The workarounds that I have come up with all have undesirables effect

Solution 1: Using triple-quoted lines and split long lines as in Multiple lines in f-string replacement field introduces the newline into the log, which is very undesirable.

Solution 2: instead of creating the temporary tuple and using = inside {}, repeat them manually
(
“(self.method1(), self.variable2, self.some_dict[‘some key’], …) = " # this ordinary string can be split further if needed
f”{self.method1()}, "
f"{self.variable2}, "

“)”
)
which becomes a maintenance nightmare as the string will gradually deviate from the rest of f-strings.

Solution 3: Don’t bother with the tuple
f"{self.method1() = }", …
which makes the log a bit more difficult to parse.

Solution 4: leave the line ultra long and silence any warnings from linters/checkers/…

My subjective vote is with 3-ish:

>>> f"{x=}, {y=}, " \
...           f"{z=}"
'x=4, y=5, z=6'

I don’t find that harder to parse (by eye or some s/w you write). Perhaps harder to filter/search if you have no other clue.

This, but without the backslashes, since any ongoing expression allows you to abut strings without a backslash.

>>> print(f"{x=}, {y=}, "
...     f"{z=}"
... )
x=4, y=5, z=6

Inside a logging call, or any other parenthesized expression, you can do this the easy way.

Without backslashes and used multiple line in fstring
x=1
y=2
z=3
print(f"“”

({x},

{y},

{z})

“”")

The best course is your solution4, just ignore PEP-8 and write whatever looks best to you. Since this code isn’t going into the standard library, nobody should care.

I think this is easier, not harder to understand visually - the purpose of this kind of debugging f-string output is to understand what each output value is about, and this is easier when the values are separately labelled. This also allows for splitting up f-strings, which can even be implicitly concatenated:

logger.info(
    f'{self.method1()=} '
    f'{self.variable2=} '
    f'{self.some_dict['some_key']=}'
)

Or explicitly joined (which makes the whitespace less tricky):

logger.info(' '.join(
    f'{self.method1()=}',
    f'{self.variable2=}',
    f'{self.some_dict['some_key']=}'
))

If you’re using the logging module infrastructure, you should avoid building the string yourself, but passing the template and arguments, and the logging infrastructure will build the string. This has two benefits:

  • actually not building the string if the log would not be output (e.g. because of level rules)

  • protect the rest of the code of template/formatting errors

For the sake of demonstration, here’s one way to construct this while actually using the logging module’s existing functionality:

logger.info(
    (
        'self.method1()=%s '
        'self.variable2=%s '
        'self.some_dict["some_key"]=%s'
    ),
    self.method1(),
    self.variable2,
    self.some_dict['some_key'],
)

This loses the benefit of the {...=} notation though. Unless the formatting is particularly costly, I would go with the f-string, as it’s a much lower maintenance burden.