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.

1 Like

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.

1 Like

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']=}'
))
2 Likes

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

1 Like

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.