How to use f string format without changing content of string?

def engine(current_money=10.0):
    # Instruction
    print(
        f"""
---------------------------------------------------------------------------
| You start with ${current_money}                                                    |
| Each play costs 25 cents.                                               |
| For each play, the slot machine will output a random three-digit number.|
| If have two similar numbers, you will earn 50 cents.                    |
| If have three similar numbers, you will earn $10.                       |
---------------------------------------------------------------------------
    """
    )

If current_money = 10.0 it will print:

---------------------------------------------------------------------------
| You start with $10.0                                                    |
| Each play costs 25 cents.                                               |
| For each play, the slot machine will output a random three-digit number.|
| If have two similar numbers, you will earn 50 cents.                    |
| If have three similar numbers, you will earn $10.                       |
---------------------------------------------------------------------------

but with current_money = 9.0 it becomes:

---------------------------------------------------------------------------
| You start with $9.0                                                    |
| Each play costs 25 cents.                                               |
| For each play, the slot machine will output a random three-digit number.|
| If have two similar numbers, you will earn 50 cents.                    |
| If have three similar numbers, you will earn $10.                       |
---------------------------------------------------------------------------

It will lose a space character in second line. How can I prevent this behavior? I want to make a function to print the text above but the value in f string depend on the length of value current_money. Is there a way to freeze a string?. Thanks for your help.

In f-strings you can specify the width of the space you want the string to take up with :<5 after the variable name. The : is beginning the format specifier, the < is saying “left-justify” and the 5 is saying “reserve 5 columns of space for this variable”. If you chose the number just right, i.e. make it exactly the length of ${current_money:<nn} then everything will line up.

So:

def engine(current_money=10.0):
    # Instruction
    print(
        f"""
---------------------------------------------------------------------------
| You start with ${current_money:<19}                                     |
| Each play costs 25 cents.                                               |
| For each play, the slot machine will output a random three-digit number.|
| If have two similar numbers, you will earn 50 cents.                    |
| If have three similar numbers, you will earn $10.                       |
---------------------------------------------------------------------------
    """
    )

should do the trick.

4 Likes

This looks like a handy option, Matt. I’d like to understand it better. Can you revise your explanation to clarify why the justification takes a column number and what it does with it? (It also looks like you revised the code to use column 19 where the displayed value starts but left 5 in the explanation.)

And do you know of a good reference for the syntax on it? This Page at docs.python just gives a few terse examples and more general search results want to talk about the format() function without the in-place colon syntax.

EDIT: I found this section at docs.python but it doesn’t seem to explain the :<X value fully.

You’ll find the reference and multiple examples in the page you linked to. Check the “Format Specification Mini-Language” section:

https://docs.python.org/3/library/string.html#formatspec

They are two different options. The first (<) is the align option. The second (the digit), is the width option. Both are optional, and both are described in the “Format Specification Mini-Language” section.

See also the section explaining the syntax for more details:

https://docs.python.org/3/library/string.html#formatstrings

Oh, and the colon marks the start of the format spec: [":" format_spec]

1 Like

Thanks, Erlend. I read the entire section before posting and unfortunately there’s only one short example of :<X and no explanation other than “Aligning the text and specifying a width”. Here it is:

'{:<30}'.format('left aligned')
'left aligned                  '

Actually, I did miss something on my first read that’s much easier to see with the Discourse code coloring: the right quote mark to capture the spaces. I prefer to use ] and [ to bracket whitespace (or reveal rogue leading or trailing whitespace) because brackets are very easy to see:

'{:<30}'.format("left aligned") + '['
left aligned                  [

An improvement: Instead of just saying “a width”, the text should be something like “…specifying the size of the text space to position the string of characters in.” And the code doesn’t produce the output shown (no single quotes; those were added manually). I’ll post these suggestions to the docs repo. Thank you for prompting me to re-read the section. :grinning:

So it looks like Matt’s choice of 19 that matches the first column number is an unfortunate coincidence. Maybe he’ll update the post so the code shows 5 again. (I do hope so.)

Actually, there are four alignment examples:

>>> '{:<30}'.format('left aligned')
'left aligned                  '
>>> '{:>30}'.format('right aligned')
'                 right aligned'
>>> '{:^30}'.format('centered')
'           centered           '
>>> '{:*^30}'.format('centered')  # use '*' as a fill char
'***********centered***********'

I don’t know; I have no problems with understanding width.

Four examples of {:...30}, yes.

I think the rabbit trail started here with the phrase “within the available space”. This statement is ambiguous because “available space” could be 80 columns or more. It’s actually a specificed space (or field).

I get it now :+1: …just working out how I got off track and how it might be expressed clearer. Between the terse wording of the docs and the mixed values in Matt’s post, it wasn’t clear the first time I read those two docs.

By Hungpham3112 via Discussions on Python.org at 15Jun2022 09:31:

def engine(current_money=10.0):
   # Instruction
   print(
       f"""
---------------------------------------------------------------------------
> You start with ${current_money}                                                    |

[…]

It will lose a space character in second line. How can I prevent this
behavior? I want to make a function to print the text above but the
value in f string depend on the length of value current_money. Is
there a way to freeze a string?. Thanks for your help.

What you want is the width specifier in format strings.

I was going to point you at the f-string doco here:
https://docs.python.org/3/reference/lexical_analysis.html#f-strings
but that is nearly unreadable, being about the grammar. in finicky
detail.

You’re probably better off looking at this:
https://docs.python.org/3/library/string.html#formatspec
which has a lot of examples. You want the width ones.

The immediate instinct (for me) is to make your ${current_money} fixed
width. For example by specifying 2 digits after the decimal point. And
you can do that.

But you have a larger problem. You want to make all the lines the same
length. I recommend embedding those lines in format strings too, eg:

target_length = 30
for line in lines:
    print(f'| {line:{target_length}} |')

You will notice that the target length itself is a format field here.

You will want to choose left or right justification, too.

Cheers,
Cameron Simpson cs@cskk.id.au

1 Like

“…recommend embedding those lines in format strings too…”
print(f'| {line:{target_length}} |')

How would the fString be included in this example from above? Parsing the formatted string as a list item obscures the fString coding (turns the iterated line into a pure string literal).

current_money = 10
lines = [" You start with ${current_money}",
         " Each play costs 25 cents."]
target_length =35
print()
for line in lines:
    print(f'| {line:{target_length}} |')
|  You start with ${current_money}    |
|  Each play costs 25 cents.          |

After reading some answers above, I create a function that return my string

def instruction(current_money=10.0):
    return f"""
---------------------------------------------------------------------------
| You start with ${current_money:<19}                                     |
| Each play costs 25 cents.                                               |
| For each play, the slot machine will output a random three-digit number.|
| If have two similar numbers, you will earn 50 cents.                    |
| If have three similar numbers, you will earn $10.                       |
---------------------------------------------------------------------------
"""

I want it to be clean as much as possible, if I turn it to a list like @mlgtechuser said before, my string will lose the first and the last line.

Testing with @mlgtechuser instruction but the string turned wrong:

def instruction(current_money=10.0):
    lst = (
        "------------------------------------------------------------------------",
        f"You start with ${current_money}",
        "Each play costs 25 cents.",
        "For each play, the slot machine will output a random three-digit number.",
        "If have two similar numbers, you will earn 50 cents.",
        "If have three similar numbers, you will earn $10.",
        "------------------------------------------------------------------------",
    )
    target_length = 72
    return map(str.rstrip, (f"| {line:{target_length}} |" for line in lst))


print(*instruction(12))

will print:

| You start with $12                                                       |
 | Each play costs 25 cents.                                                |
 | For each play, the slot machine will output a random three-digit number. |
 | If have two similar numbers, you will earn 50 cents.                     |
 | If have three similar numbers, you will earn $10.                        |
1 Like

Well you answered my question! :+1:

Now here’s the answer to the hidden question in your post above: Try using list brackets [].

[List]
{set}
(tuple) (as in ‘quintuple’, ‘sextuple’, ‘septuple’, etc.)

Yeah, but can you fix my function to make it right?

| You start with $12                                                       |    <- still have a gap in here
 | Each play costs 25 cents.                                                |
 | For each play, the slot machine will output a random three-digit number. |
 | If have two similar numbers, you will earn 50 cents.                     |
 | If have three similar numbers, you will earn $10.                        |

That is weird, I see a different behaviour. The print is supposed to print individual lines but you use the default space separator when you need newlines. Maybe you tuned the terminal width to fit your output? Try to use newlines as separators between individual print’s arguments:

print(*instruction(12), sep='\n')
1 Like

You can also prepare a template and replace the values later:

template = "You start with ${current_money}"

current_money=10.0
print(template.format(**locals()))

locals() returns a dict-like object containing variables in the local scope.

Important advice: Never-ever use a not sanitized external input to your program as a similar template. This mistake leads to serious security bugs in software.

1 Like

Of course I can. I ran it in VS Code to produce the two lines I posted above. By the way, “Thank You” is a much better way to begin a reply than “Yeah, but…”. :slightly_smiling_face:

if I turn it to a list […] my string will lose the first and the last line.

This is the problem I addressed. Your post didn’t say anything about the offset top line. I saw the missing space but it looks like a pasting error. First, though, let’s make sure you followed the code I posted and the correction to your list function. Did you try your function with Square Brackets []?

Sorry if I misunderstand you, my English is not good.

I thought so. I am in Japan now and am learning to speak Japanese. Your English is much better than my Japanese!

can you fix my function

You can do your function using lst = () or lst = [].

You do not need the return map(...).
A for: loop in your function will print the lines, like this:

target_length =75
print()
for line in lines:
    print(f'|{line:{target_length}}|')

So the whole function is…

def instruction(current_money=10):
    lines = (
        "---------------------------------------------------------------------------",
        f" You start with ${current_money:<19}",
        " Each play costs 25 cents.",
        " For each play, the slot machine will output a random three-digit number.",
        " If have two similar numbers, you will earn 50 cents.",
        " If have three similar numbers, you will earn $10.",
        "---------------------------------------------------------------------------"
    ) 
    target_length =75
    print()
    for line in lines:
        print(f'|{line:{target_length}}|')
instruction(12)

Your code is VERY different from what I posted.

Fix ONE problem at a time. Many changes = much confusion.

1 Like

I’m trying to write it in functional style. I understood that your function work to me but “print” make it has external state so I don’t like it very much