Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Python Module 2 – Values, variables, numbers and strings

f-strings and the format mini-language

Format numbers, text and dates with Python f-strings and format specifications (width, alignment, precision, grouping), debug with =, and read % and format().

  • Beginner
  • 30 minutes
  • Examples run with Python 3.14.8 and Pyodide 314.0.7
  • By MySmartCoPilot

What you will learn

  • Format numbers, dates and text with f-strings and format specs
  • Use the = specifier and !r conversion for debugging
  • Read older % and str.format code

Before you start

On this page

Programs constantly turn values into text: a total on a receipt, a percentage in a report, a date on a ticket, a variable’s value while you hunt a bug. An f-string does that in one readable line, and the format specification after a colon inside it controls the details: how wide, how many decimals, which separator, which alignment.

Values in text with f-strings

Put f (or F) before the opening quote, and write expressions inside curly braces. Python evaluates each one when the line runs and puts its value into the text:

Expressions inside braces Python · first_fstrings.py
item = "masala dosa"
price = 85
quantity = 3

# Expressions inside the braces run when the line runs, and their values are put in the text.
print(f"{quantity} × {item} cost {price * quantity}")
print(f"{item.title()} has {len(item)} characters")

# Without the f, braces are ordinary characters.
print("{quantity} × {item}")

# Doubled braces print one brace.
print(f"{{quantity}} is replaced by {quantity}")

Output

3 × masala dosa cost 255
Masala Dosa has 11 characters
{quantity} × {item}
{quantity} is replaced by 3

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 first_fstrings.py

  • Anything that is an expression can go in the braces: a name, arithmetic, a method call such as item.title(). Unless a format specification (the next section) says otherwise, each value appears as str() would write it.
  • Forget the f and the braces are printed as typed, with no error to warn you. When you see {name} in your output, look for the missing f first.
  • To print a brace itself, double it: {{ and }}.

Version note

Since Python 3.12 (PEP 701), an expression inside an f-string may use the same kind of quotes as the f-string around it, as in f"{prices["tea"]}", may contain backslashes, and may run over several lines with comments. Python 3.11 and older reject the same line with a SyntaxError, so code that must run on them uses the other kind of quotes inside: f"{prices['tea']}".

Format specifications

After the expression, a colon starts a format specification, a short code that says how the value should look. This receipt uses widths, alignment, decimals and commas:

A receipt in neat columns Python · receipt.py
order = [("Masala dosa", 2, 85), ("Filter coffee", 3, 30), ("Idli", 4, 12.5), ("Party thali", 40, 260)]

print(f"{'Item':<15}{'Qty':>5}{'Price':>9}{'Amount':>12}")
total = 0
# Each item is a tuple (name, quantity, price); the loop takes them one at a time.
for name, quantity, price in order:
    amount = quantity * price
    total += amount
    print(f"{name:<15}{quantity:>5}{price:>9.2f}{amount:>12,.2f}")
print(f"{'Total':<29}{total:>12,.2f}")
print(f"{' Thank you ':*^41}")

Output

Item             Qty    Price      Amount
Masala dosa        2    85.00      170.00
Filter coffee      3    30.00       90.00
Idli               4    12.50       50.00
Party thali       40   260.00   10,400.00
Total                           10,710.00
*************** Thank you ***************

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 receipt.py

The header line formats strings: {'Item':<15} pads “Item” with spaces to 15 characters, aligned left. The item lines format numbers: {price:>9.2f} writes the price with two decimals, right-aligned in 9 characters, and {amount:>12,.2f} adds a comma between every three digits. The last line centres a text with ^ and fills the spare room with *.

Every part of a specification is optional, but the parts that are present always come in this order:

  1. Fill and align, such as *>: < left, > right, ^ centre, and = puts the padding after the sign. Any fill character may come before the align character.
  2. Sign, such as +: + shows a sign on every number, and a space puts a space before positive numbers.
  3. z (Python 3.11 and newer): turns a negative zero after rounding, such as -0.00, into 0.00.
  4. #: the alternate form, which adds prefixes such as 0x for hexadecimal.
  5. 0: pads numbers with zeros after the sign.
  6. Width, such as 12: the minimum width in characters. Longer values are not cut.
  7. Grouping, such as ,: a comma (or _) between groups of three digits. With the types b, o, x and X, _ groups in fours instead.
  8. Precision, such as .2: the digits after the point for f, e and %, the significant digits for g, and the most characters kept for a string.
  9. Type, such as f: how to write the value, for example d, f, e, g, %, x or b.

Without an align character, strings line up on the left and numbers on the right. A width is only a minimum: a value longer than the width is printed in full, which keeps numbers correct but can push a column out of line.

Numbers

format(value, spec) applies one specification and returns the text, exactly as an f-string does after its colon. This program runs a list of specifications through it:

One value, many specifications Python · number_specs.py
# format(value, spec) applies one format specification; an f-string does the same after the colon.
examples = [
    (1234567.891, ".2f"),
    (1234567.891, ",.2f"),
    (1234567.891, "_.0f"),
    (1234567.891, ".3e"),
    (1234567.891, ".3g"),
    (0.256, ".1%"),
    (42, "+d"),
    (42, "06d"),
    (255, "x"),
    (255, "#X"),
    (5, "08b"),
    (-0.004, ".2f"),
    (-0.004, "z.2f"),
]
for value, spec in examples:
    print(f"{value!r:>11} {spec!r:7} → {format(value, spec)}")

Output

1234567.891 '.2f'   → 1234567.89
1234567.891 ',.2f'  → 1,234,567.89
1234567.891 '_.0f'  → 1_234_568
1234567.891 '.3e'   → 1.235e+06
1234567.891 '.3g'   → 1.23e+06
      0.256 '.1%'   → 25.6%
         42 '+d'    → +42
         42 '06d'   → 000042
        255 'x'     → ff
        255 '#X'    → 0XFF
          5 '08b'   → 00000101
     -0.004 '.2f'   → -0.00
     -0.004 'z.2f'  → 0.00

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 number_specs.py

  • f gives a fixed number of decimals and e scientific notation. g picks one of the two and drops zeros at the end; with .3g it keeps three significant digits.
  • % multiplies by 100 and adds a percent sign, so store fractions (0.256) rather than percentages.
  • d is a whole number, x and X hexadecimal, b binary and o octal; # adds the prefix (0X), and 08b pads with zeros to eight places.
  • Rounding to a precision follows the same rules as round(), on the binary value a float really holds, so a few decimal numbers round the “wrong” way; the floating-point lesson explains why. For money, keep whole paise or cents in an integer, or use decimal.Decimal.
  • -0.004 rounds to -0.00, which looks odd on a report; the z option writes 0.00 instead.

Version note

New in Python 3.14: the grouping option can also follow the precision, to group the digits after the decimal point. Python 3.13 and older reject these specifications with a ValueError.

Grouping digits after the point Python · fraction_grouping.py
value = 1234567.891234

print(f"{value:,f}")
print(f"{value:,._f}")
print(f"{value:_._f}")
print(f"{0.000123456789:.10_f}")

Output

1,234,567.891234
1,234,567.891_234
1_234_567.891_234
0.000_123_456_8

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 fraction_grouping.py

Lakh and crore

The , option always groups in threes, the international way: 1,234,567. Indian writing groups the last three digits and then pairs, so one lakh is 1,00,000 and the same number is 12,34,567. No option of a format specification does that on its own. The n type groups digits the way the current locale does, so an Indian locale can give that grouping, but a program must first choose one with locale.setlocale(), and the locales differ from one system to another (in the browser runner, n adds no separators at all). Programs that need Indian grouping therefore usually do it themselves, as this lesson’s exercise does.

Lakh Crore Converter (to Million, Billion) Check any amount in both groupings, and in lakh, crore, million and billion, to test your own function.

A common mistake: the wrong type code

Each type code belongs to some kinds of value. d is for integers, and a float does not convert itself silently:

d is for whole numbers Python · mistakes.py
price = 49.75

# A float with no decimals shown: the f type with precision 0 rounds it.
print(f"{price:.0f}")

# The d type is for integers only, so this line stops the program.
print(f"{price:d}")

Output (exit status 1)

50

Printed as an error (standard error)

Traceback (most recent call last):
  File "mistakes.py", line 7, in <module>
    print(f"{price:d}")
            ^^^^^^^^^
ValueError: Unknown format code 'd' for object of type 'float'

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 mistakes.py

{price:.0f} is the way to show a float without decimals (it rounds). For text, only a fill with the alignments <, > and ^, a width, a precision (which cuts the text short) and the type s make sense: {name:,} fails because commas mean nothing for a string.

Dates and times

Dates and times have their own specification language: after the colon come the strftime codes of the datetime module, such as %d for the day, %B for the month’s name and %H:%M for a 24-hour time:

Formatting a date and time Python · dates.py
from datetime import datetime

departure = datetime(2030, 3, 14, 6, 5)

# Dates and times take strftime codes after the colon.
print(f"{departure:%d %B %Y, %H:%M}")
print(f"{departure:%a %d %b}")
print(f"{departure:%Y-%m-%d %H:%M}")
print(f"Boarding at {departure:%I:%M %p}")

Output

14 March 2030, 06:05
Thu 14 Mar
2030-03-14 06:05
Boarding at 06:05 AM

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 dates.py

%Y-%m-%d gives the international ISO 8601 order, which also sorts correctly as text. The names of days and months come from the current locale. Whatever the computer’s language, a Python program starts with the plain C locale, which uses English names, until it calls locale.setlocale().

Debugging with = and !r

Add = after the expression and the f-string prints the expression’s text, an equals sign and the value. It is the quickest way to look at a few values while you hunt a bug:

The = specifier Python · debug_values.py
total = 1234.5
city = "Pune "

print(f"{total=}")
print(f"{total = :,.2f}")
print(f"{city=}")
print(f"{city!r} has {len(city)} characters")

Output

total=1234.5
total = 1,234.50
city='Pune '
'Pune ' has 5 characters

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 debug_values.py

  • The spaces you write around = are kept, so {total = } prints total = ….
  • A format specification can follow: {total = :,.2f}.
  • Without a specification, = shows the value with repr(), so a string appears in quotes and its trailing space becomes visible. The conversion !r asks for repr() anywhere (and !s for str(), !a for ascii()). Use it whenever spaces, empty strings or the difference between "7" and 7 might matter.

Reading older formatting code

f-strings arrived in Python 3.6, so older code, many libraries and a lot of answers online use the two styles that came before. They print the same result here:

Three ways to format the same line Python · old_styles.py
name = "Asha"
score = 0.8765

# printf-style formatting with %, the oldest style.
print("%s scored %.1f%%" % (name, score * 100))

# str.format(), with the same mini-language as f-strings after each colon.
print("{} scored {:.1%}".format(name, score))
print("{0} scored {1:.1%}. Well done, {0}!".format(name, score))
print("{who} scored {mark:.1%}".format(who=name, mark=score))

# The f-string that does the same.
print(f"{name} scored {score:.1%}")

Output

Asha scored 87.6%
Asha scored 87.6%
Asha scored 87.6%. Well done, Asha!
Asha scored 87.6%
Asha scored 87.6%

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 old_styles.py

  • printf-style formatting with % puts the values after the string: %s for any value as text, %d for an integer, %.1f for a float with one decimal, and %% for a percent sign. You will meet it in the logging module and in old code. The Python documentation lists its quirks: a single tuple or dictionary as the value is easy to get wrong.
  • str.format() uses braces like an f-string, with the values given as arguments: {} takes the next one, {0} a position (which may repeat) and {who} a keyword. After a colon it uses the same mini-language as f-strings. It is still useful when the template is data, such as a message read from a translation file, because an f-string must be written in the code. Use only templates you control: a field such as {0.__class__} reads an attribute of the value it is given.

Security

Never paste user input into SQL, HTML or shell commands with an f-string or format(): input such as x'; DROP TABLE users; -- would become part of the command. Databases take parameters (? placeholders in sqlite3), html.escape() makes text safe to show in HTML (between tags or in a quoted attribute), subprocess.run() given a list of arguments starts a program without a shell, so no value is read as a command, and Python 3.14’s template strings (t-strings, PEP 750) let a library see each value before it is inserted. Later lessons cover each of them.

Key takeaways

  • f"…{expression}…" puts the value of each expression into the text when the line runs; without the f, braces are printed as typed.
  • After a colon, a format specification controls the text: fill and align, sign, z, #, 0, width, grouping, precision and type, always in that order.
  • ,.2f gives two decimals with commas, % a percentage, x and b hexadecimal and binary, and <, > and ^ align within a width; Python 3.14 can also group the digits after the point.
  • , always groups in threes; other conventions, such as the Indian grouping in pairs, need your own code.
  • {value=} and !r show what a value really is while you debug.
  • % formatting and str.format() still appear in older code; format() uses the same mini-language as f-strings.

Exercise

Exercise · Medium · Python

Format rupees with Indian digit grouping

In India, large amounts group their digits in a different way from , in a format specification: the last three digits form a group, and every group before them has two digits. One lakh is 1,00,000 and one crore is 1,00,00,000.

Write format_inr(paise), which takes an amount of money as a whole number of paise (100 paise make one rupee) and returns it as rupees with that grouping and exactly two decimals:

  • format_inr(123456789) returns "₹12,34,567.89" and format_inr(100000) returns "₹1,000.00".
  • Small amounts keep a zero before the point: format_inr(99) returns "₹0.99" and format_inr(0) returns "₹0.00".
  • A negative amount has its minus sign before the rupee sign: format_inr(-150) returns "-₹1.50".

Work with whole numbers (divmod() splits the paise into rupees and the paise left over), not with floats, so that no paisa is lost to rounding. The sample tests import your function from rupees.py.

Starter code · rupees.py

def format_inr(paise):
    """Return an amount in paise as rupees with Indian digit grouping, such as '₹12,34,567.89'."""
    # Replace this line with your code.
    return ""
The sample tests · test_rupees.py
from rupees import format_inr


def test_small_amounts():
    """keeps a zero rupee before the point and two digits of paise"""
    assert format_inr(0) == "₹0.00"
    assert format_inr(5) == "₹0.05"
    assert format_inr(99) == "₹0.99"
    assert format_inr(100) == "₹1.00"


def test_thousands():
    """puts the first comma before the last three digits"""
    assert format_inr(99999) == "₹999.99"
    assert format_inr(100000) == "₹1,000.00"
    assert format_inr(9999999) == "₹99,999.99"


def test_lakh_and_crore():
    """groups the digits before the last three in pairs"""
    assert format_inr(10000000) == "₹1,00,000.00"
    assert format_inr(123456789) == "₹12,34,567.89"
    assert format_inr(1000000000) == "₹1,00,00,000.00"
    assert format_inr(98765432100) == "₹98,76,54,321.00"


def test_negative():
    """puts the minus sign before the rupee sign"""
    assert format_inr(-150) == "-₹1.50"
    assert format_inr(-123456789) == "-₹12,34,567.89"


def test_very_large():
    """keeps grouping in pairs beyond a crore, with no rounding"""
    assert format_inr(10**14 + 1) == "₹10,00,00,00,00,000.01"
    assert format_inr(123456789012345678) == "₹1,23,45,67,89,01,23,456.78"
A hint

rupees, rest = divmod(abs(paise), 100) gives the two parts, and f"{rest:02d}" writes the paise with two digits. For the grouping, turn the rupees into text with str(), split off the last three digits with digits[-3:], and then, while the part in front (digits[:-3]) is longer than two digits, keep taking two digits off its end with head[-2:] and head[:-2]. Collect the groups in a list and finish with ",".join(groups).

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Check yourself

5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 5 What does debug_values.py print?

    What does this program print? Choose one answer.

    total = 1234.5
    city = "Pune "
    
    print(f"{total=}")
    print(f"{total = :,.2f}")
    print(f"{city=}")
    print(f"{city!r} has {len(city)} characters")
    Show the answer to question 1

    Answer: it prints

    total=1234.5
    total = 1,234.50
    city='Pune '
    'Pune ' has 5 characters

    {total=} prints the expression, an equals sign and the value, and keeps the spaces written around =. Without a format specification the value is shown with repr(), so the string appears in quotes and its trailing space is visible; !r asks for repr() explicitly. The space counts as a character, so the length is 5.

  2. Question 2 of 5 What does this print?

    Read the code, then choose one answer.

    print(f"{'Menu':*^10}")
    Show the answer to question 2

    Answer: ***Menu***

    * is the fill character, ^ centres the text and 10 is the width. "Menu" has 4 characters, so the 6 left over are split evenly, three on each side.

  3. Question 3 of 5 x is 3.14159. Which f-string gives exactly " 3.14", four spaces and then 3.14?

    Choose one answer.

    Show the answer to question 3

    Answer: f"{x:8.2f}"

    Numbers are right-aligned by default, so a width of 8 with precision 2 pads 3.14 with four spaces on the left. < would align it left, a 0 before the width pads with zeros (00003.14), and the width must come before the precision, so .2f8 is not a valid specification.

  4. Question 4 of 5 This line comes from older code. What does it print?

    Read the code, then choose one answer.

    print("{0}-{1}-{0}".format("ping", "pong"))
    Show the answer to question 4

    Answer: ping-pong-ping

    The numbers in the braces are positions in the arguments of format(), and a position can be used more than once, so {0} is "ping" both times.

  5. Question 5 of 5 Put these parts of a format specification in the order Python reads them.

    Give each item its position, from 1 (first).

    Show the answer to question 5

    Answer:

    1. fill and align, such as *>
    2. sign, such as +
    3. width, such as 12
    4. grouping, such as a comma
    5. precision, such as .2
    6. type, such as f

    A specification such as *>+12,.2f is read from left to right: fill and align, then the sign, the width, the grouping, the precision and finally the type. Every part is optional, but those that are there keep this order.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.