Search code examples
pythonstringf-string

Insert newline after equals sign in self documenting f-string


With python3.8, a new feature is self documenting format strings. Where one would normally do this:

>>> x = 10.583005244
>>> print(f"x={x}")
x=10.583005244

One can now do this, with less repetition:

>>> x = 10.583005244
>>> print(f"{x=}")
x=10.583005244

This works very well for one line string representations. But consider the following scenario:

>>> import numpy as np
>>> some_fairly_long_named_arr = np.random.rand(4,2)
>>> print(f"{some_fairly_long_named_arr=}")
some_fairly_long_named_arr=array([[0.05281443, 0.06559171],
       [0.13017109, 0.69505908],
       [0.60807431, 0.58159127],
       [0.92113252, 0.4950851 ]])

Here, the first line does not get aligned, which is (arguably) not desirable. I would rather prefer the output of the following:

>>> print(f"some_fairly_long_named_arr=\n{some_fairly_long_named_arr!r}")
some_fairly_long_named_arr=
array([[0.05281443, 0.06559171],
       [0.13017109, 0.69505908],
       [0.60807431, 0.58159127],
       [0.92113252, 0.4950851 ]])

Here, the first line of the output is aligned as well, but it defeats the purpose of not repeating the variable name twice in the print statement.

The example is a numpy array, but it could have been a pandas dataframe etc. as well.

Hence, my question is: Can a newline character be inserted after the = sign in self documenting strings?

I tried to add it like this, but it does not work:

>>> print(f"{some_fairly_long_named_arr=\n}")
SyntaxError: f-string expression part cannot include a backslash

I read the docs on format-specification-mini-language, but most of the formatting there only works for simple data types like integers, and I was not able to achieve what I wanted using those that work.


Solution

  • Python 3.12+

    Python 3.12 introduced syntactic formalization of f-strings, which allows for nested f-strings. This allows us to use the following construct to achieve what we want.

    NL = '\n'
    print(f"{f"{NL}{some_fairly_long_named_arr}" = !s}")
    

    Which outputs:

    f"{NL}{some_fairly_long_named_arr}" =
    [[0.26616956 0.59973262]
     [0.86601261 0.10119292]
     [0.94125617 0.9318651 ]
     [0.10401072 0.66893025]]
    

    Python <3.12

    Multi-line solution

    I figured out a way to accomplish what I wanted, after reading through the CPython source:

    import numpy as np
    some_fairly_long_named_arr = np.random.rand(4, 2)
    print(f"""{some_fairly_long_named_arr =
    }""")
    

    Which produces:

    some_fairly_long_named_arr = 
    array([[0.23560777, 0.96297907],
           [0.18882751, 0.40712246],
           [0.61351814, 0.1981144 ],
           [0.27115495, 0.72303859]])
    

    I would rather prefer a solution that worked in a single line, but this seems to be the only way for now. Perhaps another way will be implemented in a later python version.

    However note that the indentation on the continuation line has to be removed for the above mentioned method, as such:

        # ...some code with indentation...
        print(f"""{some_fairly_long_named_arr =
    }""")
        # ...more code with indentation...
    

    Otherwise, the alignment of the first line is broken again.

    I tried using inspect.cleandoc and textwrap.dedent to alleviate this, but could not manage to fix the indentation issue. But perhaps this is the subject of another question.

    Single-line solution

    I found this after reading this article:

    f_str_nl = lambda object: f"{chr(10) + str(object)}"  # add \n directly
    # f_str_nl = lambda object: f"{os.linesep + str(object)}"  # add \r\n on windows
    
    print(f"{f_str_nl(some_fairly_long_named_arr) = !s}")
    

    which outputs:

    f_str_nl(some_fairly_long_named_arr) = 
    [[0.26616956 0.59973262]
     [0.86601261 0.10119292]
     [0.94125617 0.9318651 ]
     [0.10401072 0.66893025]]
    

    The only caveat is that the name of the object gets prepended by the name of the custom lambda function, f_str_nl.

    I also found that a similar question was already asked here.