Previous | Tutorial index | Next

Tutorial 3: Constructing and Formatting Strings with f-strings and .format()

Learning Objectives

Construct and format strings with the f prefix and the format method.

Overview

String formatting is the art of injecting dynamic values—variables, expressions, and calculations—into static text. It is essential for generating user‑friendly output, building log messages, creating reports, and preparing data for storage or transmission. While you could concatenate strings with + (e.g., "Hello " + name + "!"), this quickly becomes messy, error‑prone, and inefficient for complex outputs.

This tutorial focuses on the two modern approaches to string formatting in Python:

  1. f‑strings (Formatted String Literals) – introduced in Python 3.6, they are the most readable, concise, and performant option.
  2. The .format() method – introduced in Python 3.0, it is powerful, flexible, and still widely used, especially in codebases that need to support older Python versions.

We will also demystify the Formatting Mini‑Language – the set of specifiers that control alignment, padding, numeric precision, and thousands separators.

1. The Problem with Old‑Style Formatting (A Quick Look Back)

Before f‑strings and .format(), Python used the % operator (inspired by C's printf). It works, but it is limited and less readable.

name = "Alice" age = 30 print("Name: %s, Age: %d" % (name, age)) # Clunky and error‑prone with type mismatches

Why we don't use it in new code: it struggles with complex data types, has limited formatting options, and the syntax is inconsistent. We mention it only so you recognize it in legacy code.

2. The .format() Method – The Workhorse

The .format() method is called on a string that contains curly braces {} as placeholders. You pass the replacement values as arguments, and Python fills the braces in order (or by index/keyword).

2.1 Basic Usage: Positional Arguments

name = "Bob" score = 85 print("Student: {}, Score: {}".format(name, score)) # Student: Bob, Score: 85

The first {} takes the first argument (name), the second {} takes the second (score).

2.2 Using Index Numbers for Reordering

You can explicitly number the placeholders to reorder or reuse values.

print("{1} is {0} years old.".format(25, "Charlie")) # Charlie is 25 years old. # {0} is 25, {1} is "Charlie" print("{0} {0} {1}".format("hi", "bye")) # hi hi bye

2.3 Keyword Arguments

You can pass named arguments to .format() for extra clarity.

print("User: {user}, ID: {uid}".format(user="Alice", uid=1024)) # Output: User: Alice, ID: 1024

2.4 Unpacking Dictionaries

A common pattern is to use ** to unpack a dictionary as keyword arguments.

info = {"name": "Eve", "job": "Engineer"} print("{name} works as an {job}".format(**info)) # Output: Eve works as an Engineer

2.5 Accessing List/Tuple Elements by Index

You can also index into list-like arguments directly inside the placeholders (without using **).

data = [10, 20, 30] print("First: {0[0]}, Last: {0[2]}".format(data)) # First: 10, Last: 30

3. f‑strings – The Modern Standard (Python 3.6+)

f‑strings (short for "formatted string literals") are the preferred way to format strings today. By prefixing a string with f or F, you can embed variables and expressions directly inside {}. They are evaluated at runtime.

3.1 Embedding Variables

name = "Diana" city = "Paris" print(f"{name} lives in {city}.") # Diana lives in Paris.

3.2 Embedding Expressions

Any valid Python expression can go inside the braces—arithmetic, function calls, method chaining, etc.

x = 5 y = 10 print(f"{x} + {y} = {x + y}") # 5 + 10 = 15 words = ["Hello", "World"] print(f"Joined: {', '.join(words)}") # Joined: Hello, World print(f"Length of name: {len(name)}") # Length of name: 5

3.3 f‑strings with Dictionaries and Attributes

person = {"name": "Frank", "age": 42} print(f"{person['name']} is {person['age']} years old.") class User: def __init__(self, name): self.name = name u = User("Grace") print(f"User: {u.name}")

3.4 The Debugging = Specifier (Python 3.8+)

A fantastic shortcut for debugging: f"{var=}" expands to the variable name, an equals sign, and its value.

score = 95.678 print(f"{score=}") # score=95.678 print(f"{score=:.2f}") # score=95.68 (combines with formatting!)

This saves you from typing print(f"score = {score}").

3.5 Multiline f‑strings

You can use triple quotes to create multi‑line formatted strings.

name = "Isaac" age = 35 message = f""" Hello {name}, You are {age} years old. Welcome aboard! """ print(message)

3.6 Quotes Inside f‑strings

You can freely mix single and double quotes inside the string, as long as they don't conflict with the outer quotes.

print(f"She said: \"Hello {name}!\"") # Works fine

Important Note: f‑strings cannot contain backslashes inside the expression part (e.g., f"{1\n2}" is invalid). You can, however, use backslashes in the string part outside the braces.

4. The Formatting Mini‑Language (The Specifiers)

Both f‑strings and .format() use the same mini‑language inside the {} braces. The general syntax is: {value:[[fill]align][sign][#][0][width][grouping_option][.precision][type]}

We'll cover the most practical parts:

4.1 Alignment and Padding

The width defines the minimum field size. You can also specify a fill character (default is space).

text = "Python" print(f"{text:>10}") # ' Python' (right align in 10 spaces) print(f"{text:<10}") # 'Python ' (left align) print(f"{text:^10}") # ' Python ' (center) print(f"{text:*^10}") # '**Python**' (fill with *)

With .format():

print("{:>10}".format(text)) # ' Python'

4.2 Number Formatting: Precision and Types

pi = 3.14159265 print(f"{pi:.3f}") # 3.142 print(f"{pi:.2%}") # 314.16% (3.1415 * 100) print(f"{pi:.2e}") # 3.14e+00 # Using .format() print("{:.3f}".format(pi)) # 3.142

4.3 Thousands Separators (Grouping)

Use a comma , or underscore _ to group digits.

num = 1234567890 print(f"{num:,}") # 1,234,567,890 print(f"{num:_}") # 1_234_567_890

4.4 Combining Everything

You can chain specifiers. Order matters (usually: fill/align, width, grouping, precision, type).

value = 12345.6789 print(f"{value:>15,.2f}") # ' 12,345.68' (right align, width 15, commas, 2 decimals) # With .format() print("{:>15,.2f}".format(value))

4.5 Dynamic Width/Precision (Nesting)

You can use variables to control width or precision inside the braces.

width = 10 precision = 3 x = 1.23456 print(f"{x:^{width}.{precision}f}") # ' 1.235 ' (center in width 10 with 3 decimals) # In .format(), you can nest: print("{:^{}.{}f}".format(x, width, precision))

4.6 Zeros Padding

Use 0 instead of a fill character for numeric zero‑padding.

print(f"{7:05d}") # 00007 print(f"{7:0>5d}") # 00007 (equivalent)

4.7 Sign Display

Use + to always show a sign, - only for negative (default), and space for a space before positive numbers.

print(f"{5:+d}") # +5 print(f"{-5:+d}") # -5 print(f"{5: d}") # ' 5' (space)

5. Formatting Dates and Times

This is a common real‑world use case. Combine datetime objects with the mini‑language (or use strftime).

from datetime import datetime now = datetime.now() print(f"{now:%Y-%m-%d %H:%M:%S}") # 2026-08-11 10:30:45 (example) print("{:%B %d, %Y}".format(now)) # August 11, 2026

6. When to Use Which?

Feature f‑strings (Python 3.6+) .format()
Readability ⭐⭐⭐⭐⭐ (most readable) ⭐⭐⭐⭐
Performance Fastest (evaluated at compile time) Slightly slower
Dynamic Formatting Can use variables inside specifier (e.g., {x:^{width}}) Yes, via nesting {:{}}
Logging Caution: f‑strings are evaluated immediately, even if the log level is lower (can be wasteful). Use % or .format() with logging module to defer evaluation. Good for logging (evaluation deferred)
Backward Compatibility Requires Python 3.6+ Python 3.0+ (widely supported)

General Rule: Use f‑strings for most day‑to‑day formatting. Use .format() when you need dynamic template strings (e.g., templates loaded from a file) or when maintaining a codebase that runs on Python < 3.6.

📝 Quiz – Check Your Understanding

  1. What is the output of f"{2 ** 5}"?

    Answer(A) `32`
  2. Given s = "hello", what does f"{s.upper()}" produce?

    Answer(B) `"HELLO"`
  3. What does the expression f"{3.14159:.3f}" evaluate to?

    Answer(A) `'3.142'`
  4. True or False: .format() can take both positional and keyword arguments in the same call.

    AnswerTrue
  5. What is the output of "{1} {0}".format("world", "hello")?

    Answer(B) `"world hello"`
  6. Which f‑string specifier would you use to right‑align a string in a field of width 10?

    Answer(B) `{s:>10}`
  7. What does f"{1234567:,}" output?

    Answer(A) `'1,234,567'`
  8. In Python 3.8+, what is the output of x = 5; f"{x=}"?

    Answer(B) `"x=5"` (no space)
  9. How do you format the number 0.1234 as a percentage with two decimal places using an f‑string?

    Answer(A) `f"{0.1234:.2%}"`
  10. Which method is generally preferred for new Python code (Python 3.6+)?

    Answer(C) f‑strings

💻 Exercises – Practice Makes Perfect

Exercise 1: Receipt Printer
Write a program that takes a product name (string), a quantity (int), and a unit price (float). Print a nicely formatted receipt line that:

Product Qty Total Laptop 3 3,599.97
Sample Solution ```python product = input("Product: ") qty = int(input("Quantity: ")) price = float(input("Unit price: ")) total = qty * price print(f"{product:<20}{qty:>5} {total:>10,.2f}") ```

Exercise 2: Dynamic Data Table
Given a list of tuples: data = [("Alice", 85, 92), ("Bob", 78, 88), ("Charlie", 95, 90)] (name, midterm, final).
Write a script that prints a header and each row using f‑strings, with:

Sample Solution ```python data = [("Alice", 85, 92), ("Bob", 78, 88), ("Charlie", 95, 90)] print(f"{'Name':^10} {'Mid':>5} {'Final':>5} {'Avg':>8}") for name, mid, final in data: avg = (mid + final) / 2 print(f"{name:^10} {mid:>5} {final:>5} {avg:>8.1f}") ```

Exercise 3: User Profile Builder
Create a profile dictionary with keys first_name, last_name, age, height_m.
Write f‑strings that produce:

Sample Solution ```python profile = {"first_name": "John", "last_name": "Doe", "age": 30, "height_m": 1.753} print(f"First: {profile['first_name']} | Last: {profile['last_name']} | Age: {profile['age']}") print(f"Height: {profile['height_m']:.2f}m") print(f"{profile['first_name']} {profile['last_name']} is {profile['age']} years old and {profile['height_m']:.2f} meters tall.") ```

Exercise 4: Date Formatter
Write a function format_date(year, month, day) that returns two formatted strings using both f‑strings and .format() (try both):

  1. YYYY-MM-DD (e.g., 2026-08-11)
  2. Month DD, YYYY (e.g., August 11, 2026) – you'll need to map month numbers to names.
Sample Solution ```python def format_date(year, month, day): # Using f-strings iso = f"{year}-{month:02d}-{day:02d}" months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"] long = f"{months[month-1]} {day}, {year}" # Using .format() iso2 = "{}-{:02d}-{:02d}".format(year, month, day) long2 = "{:s} {:d}, {:d}".format(months[month-1], day, year) return (iso, long, iso2, long2) ```

Exercise 5: Convert Between Methods
Rewrite the following print() calls using the other method:
a) print("Hello, {}!".format(name)) → convert to f‑string.
b) print(f"{item}: ${price:.2f}") → convert to .format().

Sample Solution ```python # a) print(f"Hello, {name}!") # b) print("{}: ${:.2f}".format(item, price)) ```

🏠 Homework – Deeper Thinking

Short Answer Questions

1. Grade Report Generator
Write a script that reads a text file containing student records in the format:
Name, Homework1, Homework2, Midterm, Final
(Simulate the file by using a multi‑line string or a list of strings).
Calculate the final grade as 0.2*HW1 + 0.2*HW2 + 0.3*Midterm + 0.3*Final.
Generate a report formatted as follows:

------------------------------------------------------------ Name HW1 HW2 Mid Final Final % ------------------------------------------------------------ Alice 95 80 85 90 87.50% Bob 70 75 80 85 78.75% ...

Align all columns neatly. The Final % column should show a percentage with 2 decimals (e.g., 87.50%). Include a footer with the class average.

Sample Answer ```python data = """Alice,95,80,85,90 Bob,70,75,80,85 Charlie,90,88,95,92""" lines = data.strip().split('\n') records = [] for line in lines: name, hw1, hw2, mid, final = line.split(',') hw1, hw2, mid, final = map(int, [hw1, hw2, mid, final]) final_grade = 0.2*hw1 + 0.2*hw2 + 0.3*mid + 0.3*final records.append((name, hw1, hw2, mid, final, final_grade)) print("-" * 60) print(f"{'Name':<20}{'HW1':>4} {'HW2':>4} {'Mid':>4} {'Final':>4} {'Final %':>8}") print("-" * 60) for name, hw1, hw2, mid, final, grade in records: print(f"{name:<20}{hw1:>4} {hw2:>4} {mid:>4} {final:>4} {grade:>8.2f}%") avg = sum(r[-1] for r in records) / len(records) print("-" * 60) print(f"Class average: {avg:>51.2f}%") ```

2. Dynamic Text Alignment Function
Write a function format_column(text, width, alignment='left', fill=' ') that uses f‑strings (or .format()) to return the text formatted inside a field of the given width. The alignment parameter can be 'left', 'right', or 'center'. Do not use manual string padding (like + or *); use the format specifiers dynamically.

Sample Answer ```python def format_column(text, width, alignment='left', fill=' '): if alignment == 'left': align_char = '<'elifalignment == 'right':align_char = '>' elif alignment == 'center': align_char = '^' else: raise ValueError("alignment must be 'left', 'right', or 'center'") return f"{text:{fill}{align_char}{width}}" ```

3. Invoice Generator
Create an invoice formatter. Given the following data:

invoice = { "number": "INV-101", "date": "2026-08-11", "customer": "Acme Corp", "items": [ {"desc": "Widget", "qty": 4, "price": 9.99}, {"desc": "Gadget", "qty": 2, "price": 19.95}, {"desc": "Doodad", "qty": 1, "price": 45.00} ], "tax_rate": 0.08 }

Write a program that prints a structured invoice:

=== INVOICE #INV-101 (2026-08-11) === Customer: Acme Corp ----------------------------------- Description Qty Unit Price Total Widget 4 9.99 39.96 Gadget 2 19.95 39.90 Doodad 1 45.00 45.00 ----------------------------------- Subtotal: 124.86 Tax (8.00%): 9.99 TOTAL: 134.85

Align the amounts to the right, with 2 decimal places and commas for thousands (if applicable). The tax rate should show as 8.00%.

Sample Answer ```python def print_invoice(inv): print(f"=== INVOICE #{inv['number']} ({inv['date']}) ===") print(f"Customer: {inv['customer']}") print("-" * 35) print(f"{'Description':<12}{'Qty':>3} {'Unit Price':>10} {'Total':>10}") subtotal = 0 for item in inv['items']: total = item['qty'] * item['price'] subtotal += total print(f"{item['desc']:<12}{item['qty']:>3} {item['price']:>10.2f} {total:>10.2f}") tax = subtotal * inv['tax_rate'] total = subtotal + tax print("-" * 35) print(f"{'Subtotal:':>29} {subtotal:>10.2f}") print(f"Tax ({inv['tax_rate']*100:.2f}%):{'':>18} {tax:>10.2f}") print(f"{'TOTAL:':>29} {total:>10.2f}") ```

Essay Questions

4. Logger Formatter with .format()
Write a logging utility that uses a template string (stored in a variable) and the .format() method. The template should support placeholders for timestamp, level, module, and message.
Create a list of log entries (as dictionaries). Iterate through them and print formatted log lines. Demonstrate how .format() allows you to load templates dynamically (e.g., from a config file) – something f‑strings cannot easily do.

Sample Answer ```python template = "{timestamp} [{level}] {module}: {message}" logs = [ {"timestamp": "2026-08-11 10:00", "level": "INFO", "module": "auth", "message": "User logged in"}, {"timestamp": "2026-08-11 10:05", "level": "ERROR", "module": "db", "message": "Connection failed"}, ] for entry in logs: print(template.format(**entry)) # The template can be loaded from a file: with open('log_format.txt') as f: template = f.read() ```

5. Nested Formatting Challenge
You have a list of floating‑point numbers: [0.001, 0.12345, 123.456, 1e-5].
Using a single f‑string inside a loop, print each number in three formats side‑by‑side in a table:

  1. Fixed with 4 decimals (right‑aligned, width 12).
  2. Scientific with 3 significant decimals (right‑aligned, width 12).
  3. Percentage with 2 decimals (right‑aligned, width 10).
    The numbers must be dynamically processed in a single loop.
Sample Answer ```python nums = [0.001, 0.12345, 123.456, 1e-5] for num in nums: print(f"{num:>12.4f} {num:>12.3e} {num:>10.2%}") ```

Homework Hints

Summary

You are now equipped with two powerful string formatting tools:

You also understand the mini‑language specifiers that control alignment, padding, numeric precision, grouping, and signs. With these skills, you can produce professional, clean, and dynamic textual outputs for any application—from console scripts to full‑blown report generators.

Next Steps: In Tutorial 4, we will explore Lists in Depth, covering list comprehensions, copying, and advanced iteration techniques.

Happy formatting!

Previous | Tutorial index | Next