Addaly is in open beta. Things will change, and AI answers can be wrong — check anything that matters.

Python, From Zero, For AI

From your first line of code to your first API call.

Lesson 7 of 897 min

Building the output a person will read

f-strings, and why the plus sign fails

You want to print a sentence with a value in it. The obvious attempt:

python
name = "Asha"
owed = 1250
print("Hello " + name + ", you owe " + owed)
TypeError: can only concatenate str (not "int") to str

+ between two strings joins them; between a string and an int it has no defined meaning, so Python refuses rather than guessing. You could wrap it in str(owed), and people did for years. The modern way is an f-string: put an f before the opening quote and put expressions in braces.

python
print(f"Hello {name}, you owe {owed}")

Anything can go inside the braces — a variable, arithmetic, a method call, an index:

python
print(f"{name.upper()} owes {owed * 1.18:.2f} after tax")

The part after the colon is a format specification, and it is where most of the value is.

The format specifications worth memorising

python
value = 1234567.8915

f"{value:.2f}"      # '1234567.89'   two decimal places
f"{value:,.2f}"     # '1,234,567.89' thousands separators
f"{0.0734:.1%}"     # '7.3%'         as a percentage
f"{42:05d}"         # '00042'        zero-padded to width 5
f"{'total':>10}"    # '     total'   right-aligned in 10 columns
f"{'total':<10}"    # 'total     '   left-aligned
f"{'total':^10}"    # '  total  '    centred
f"{255:x}"          # 'ff'           hexadecimal

Alignment is what turns a wall of numbers into something readable in a terminal:

python
for item, cost in [("rice", 340), ("dal", 1250), ("oil", 92)]:
    print(f"{item:<10}{cost:>8,}")
rice           340
dal          1,250
oil             92

The debugging form almost nobody knows

Put = at the end of the expression:

python
>>> count = 7
>>> print(f"{count = }")
count = 7
>>> print(f"{count * 2 = }")
count * 2 = 14

It prints the expression and its value. When you are chasing a wrong number, this halves the typing and removes the classic mistake of labelling one variable with another variable's name. It needs Python 3.8 or newer.

Rounding for display is not rounding the value

python
price = 19.999
print(f"{price:.2f}")   # 20.00
print(price)            # 19.999

The f-string produced a string. The variable is untouched. This is the right way round: keep full precision in the data, decide on presentation at the edge. Programs that call round() on the way in lose information they cannot get back.

The honest limitation: grouping is not local

{:,} gives 1,234,567. It does not give 12,34,567, which is how that number is written across India — grouping in lakhs and crores after the first thousand. The comma specifier is hard-coded to groups of three.

To get local grouping you need the locale module and an en_IN locale actually installed on the machine, which on a stripped-down server or a phone it often is not:

python
import locale
locale.setlocale(locale.LC_ALL, "en_IN.UTF-8")   # may raise locale.Error
locale.format_string("%d", 1234567, grouping=True)

If that raises, and it will on many systems, write the grouping yourself with a small function, or use the babel package, which carries its own locale data and does not depend on the operating system. This is a real constraint, not a footnote: reports that show Indian currency in Western grouping look wrong to everyone reading them.

Older forms you will meet in other people's code

python
"Hello %s, you owe %d" % (name, owed)      # the oldest, still in logging
"Hello {}, you owe {}".format(name, owed)  # the middle era

Both still work. Write f-strings in new code, and read the other two without panic. One exception: the logging module wants the %s form passed as separate arguments, for a reason covered when we get to logging.

print has two settings worth knowing

python
print("a", "b", "c")                  # a b c
print("a", "b", sep="")               # ab
print("loading", end="")              # no newline afterwards
print(*["rice", "dal", "oil"], sep="\n")

sep is what goes between the arguments, end is what goes after the last one, and it defaults to a newline. end="" is how you build a progress line that overwrites itself rather than scrolling. A triple-quoted string spans lines, which is often simpler than three separate print calls for a fixed block of text.

Two small traps

  • A literal brace needs doubling: f"{{literal}}" prints {literal}.
  • An f-string is built the moment the line runs. f"{total}" captures the value of total right then, not later.

Try this now

Print a three-column table of any five items you own, with the name left-aligned in 12 columns, a quantity right-aligned in 4, and a price with two decimals and thousands separators. Getting the columns to line up teaches the specifiers faster than reading them.

The one thing to keep

An f-string builds a string at the moment it runs, and the part after the colon controls only how the value is displayed, never the value itself.

Before you move on

A report shows `f"{total:.2f}"` for every row and the printed column sums to 1 paisa less than the stored total. What is the correct account of this?

Pick the one you would defend. Nobody sees your answer.

No ads. No data sale. No public scores on people. Ever.

© 2026 Addaly