Skip to content
💻 🧠 Code 1001 > 📑 Cheat Sheets > 🐍 Python Cheat Sheets > Lesser-known f-string features you might not know about

Lesser-known f-string features you might not know about

<em># Create two variables with numeric values.</em>
one = 1
two = 2

<em># Insert the values of 'one' and 'two' into the string.</em>
f"{one}, {two}"
<em># Output: '1, 2'</em>

The power of f-strings lies in their ability to contain not just variables, but almost any valid Python expression inside the braces. This allows for on-the-fly calculations without creating unnecessary intermediate variables.

<em># Perform addition directly inside the f-string.</em>
f"{one} + {two} = {one + two}"
<em># Output: '1 + 2 = 3'</em>

You can easily access elements from data structures like dictionaries (by key) and lists (by index) from within an f-string. This also extends to accessing object attributes (using dot notation) and even calling functions.

<em># Accessing a dictionary value by key.</em>
colors = {"red": "#ff0000", "green": "#00ff00"}
f"The color red is {colors['red']}"
<em># Output: 'The color red is #ff0000'</em>

<em># Accessing list elements by index.</em>
data = [4, 8, 15, 16, 23, 42]
f"Best numbers: {data[4]} and {data[5]}"
<em># Output: 'Best numbers: 23 and 42'</em>

<em># Accessing object attributes and calling functions.</em>
from dataclasses import dataclass
@dataclass
class Point:
    x: int
    y: int
pos = Point(23, 42)
f"Position: {pos.x}, {pos.y}. The larger value is {max(pos.x, pos.y)}."
<em># Output: 'Position: 23, 42. The larger value is 42.'</em>

Using f-strings for Debugging

Since Python 3.8, f-strings have included a powerful tool for quick debugging. By placing an equals sign (=) after a variable or expression inside the braces, you instruct Python to output not just the value, but the expression itself. This is incredibly handy for inspecting code.

from datetime import date, timedelta
user = "eric_idle"
member_since = date(1975, 7, 31)
delta = date(2022, 4, 11) - member_since

<em># Use f"{user=}" to automatically get "user='eric_idle'".</em>
f"{user=} {member_since=}"
<em># Output: "user='eric_idle' member_since=datetime.date(1975, 7, 31)"</em>

This debugging specifier can be combined with regular formatting specifiers. You can also use it on entire expressions to clearly show both the calculation and its result.

<em># = shows the variable name, : starts formatting, ,d formats as a comma-separated integer.</em>
f"{delta.days=:,d}"
<em># Output: 'delta.days=17,056'</em>

<em># Show both the input and the result of a calculation.</em>
from math import cos, radians
theta = 30
f"{theta=} {cos(radians(theta))=:.3f}"
<em># Output: 'theta=30 cos(radians(theta))=0.866'</em>

Printing Debug Representations (repr)

Python objects have two string representations: str() (for user-friendly output) and repr() (for developer-friendly, unambiguous output). By default, f-strings use str(). To get the repr() output, you can use the !r conversion flag, which is a convenient shortcut for calling repr() manually. The !s flag explicitly calls str().

class Data:
    def __repr__(self): return '<Data ...>' <em># Debug representation</em>
    def __str__(self): return 'string representation' <em># User representation</em>
data = Data()

<em># Default behavior calls str(data)</em>
f"data: {data}"
<em># Output: 'data: string representation'</em>

<em># Using the !r flag calls repr(data)</em>
f"data: {data!r}"
<em># Output: 'data: <Data ...>'</em>

Padding, Alignment, and Truncation

F-strings provide full control over output formatting, including field width, alignment, and fill characters. The format specifier comes after a colon (:).

  • > right-aligns text.
  • < left-aligns text.
  • ^ centers text.
  • A number after the alignment character specifies the total width of the field.
  • A character before the alignment character specifies the fill character.
val = "test"

<em># Right-align within a 10-character field</em>
f"{val:>10}"
<em># Output: '      test'</em>

<em># Left-align and fill the empty space with '_'</em>
f"{val:_<10}"
<em># Output: 'test______'</em>

<em># Center the text</em>
f"{val:^10}"
<em># Output: '   test   '</em>

To truncate a string to a maximum length, use the syntax :.N, where N is the number of characters.

instrument = "xylophone"
<em># Truncate the string to 5 characters.</em>
f"{instrument:.5}"
<em># Output: 'xylop'</em>

Number Representations

Numerous formatting options are available for numbers. You can easily convert numbers to binary (b), octal (o), or hexadecimal (x/X). Using a # adds the appropriate prefix (0b0o0x).

answer = 42

<em># Hexadecimal representation</em>
f"{answer:x}, {answer:X}"
<em># Output: '2a, 2A'</em>

<em># With a prefix</em>
f"{answer:#x}"
<em># Output: '0x2a'</em>

<em># Scientific notation</em>
f"{answer ** 8:e}"
<em># Output: '9.682652e+12'</em>

Padding numbers with leading zeros is done by using 0 as a fill character. For floats, you can control both the total width and the number of decimal places. You can also control the sign display and add thousands separators.

<em># Pad to 4 digits with leading zeros</em>
f"{answer:04d}"
<em># Output: '0042'</em>

<em># For Pi: total width 6, 2 decimal places, pad with zeros</em>
import math
f"{math.pi:06.2f}"
<em># Output: '003.14'</em>

<em># Always show the sign (+ for positive, - for negative)</em>
f"{answer:+d}"
<em># Output: '+42'</em>

<em># Use a comma as a thousands separator</em>
num = 1234567890
f"{num:,d}"
<em># Output: '1,234,567,890'</em>

Additional Topics and Best Practices

Many built-in objects, like datetime, have their own formatting mini-language that works directly inside f-strings.

from datetime import datetime
dt = datetime(2022, 4, 11, 13, 37)
<em># Use standard date/time formatting codes</em>
f"{dt:%Y-%m-%d %H:%M}"
<em># Output: '2022-04-11 13:37'</em>

Format specifiers themselves don’t have to be static! You can set them dynamically using variables with nested curly braces.

<em># Width and alignment are set by variables</em>
value = "test"
align = "^"
width = 10
<em># f'{value:{align}{width}}' first becomes f'{value:^10}'</em>
f'{value:{align}{width}}'
<em># Output: '   test   '</em>

If you need to include a literal curly brace { or } in your string, simply double it.

f"Literal braces: {{value}}"
<em># Output: 'Literal braces: {value}'</em>

Practical Advice and Pitfalls

Readability vs. Brevity

F-strings can embed very complex expressions, but this is not always a good idea. A core tenet of Python is readability.

Bad Practice (hard to read):

<em># Technically works, but what's happening here?</em>
print(f"Result: {round([user.height for user in users if user.active][0] / 2.54, 2)} inches.")

Good Practice (separating logic from presentation):

<em># 1. First, prepare the data</em>
active_users_heights = [user.height for user in users if user.active]
first_user_height_cm = active_users_heights[0]
height_in_inches = round(first_user_height_cm / 2.54, 2)

<em># 2. Then, format the result</em>
print(f"Result: {height_in_inches} inches.")

The code is longer, but it’s now understandable, testable, and easy to modify. A general rule: if an expression inside {} is longer than 20-30 characters or contains complex logic, it’s better to extract it into a separate variable.

Multi-line f-strings

For long strings or generating multi-line text (e.g., SQL queries, HTML templates), use triple quotes. Expressions inside can also be multi-line.

user = "hypo69"
permissions = ['read', 'write', 'execute']

<em># The expression inside the f-string is split across multiple lines for readability</em>
report = f"""
User Report: {user.upper()}
----------------------------------
Permissions: {', '.join(p for p in permissions
                            if p != 'execute')}
"""
print(report)

Security Concerns (Very Important!)

F-strings themselves are safe. An expression like f"Hello, {name}" is not a vulnerability; Python simply takes the string representation of name.

The danger arises when you combine f-strings with functions that execute code, like eval().

EXTREMELY DANGEROUS CODE — NEVER DO THIS:

import os
user_template = input("Enter a template: ") <em># User enters: '{os.system("reboot")}'</em>
eval(f'f"{user_template}"')

If a user provides {os.system("reboot")}, your server will reboot. This is a Remote Code Execution (RCE) vulnerability. Never pass user-supplied strings into functions like eval(), even via f-strings. Use secure templating engines like Jinja2 for user-defined templates.

Performance: Why are f-strings so fast?

An f-string is not a function call; it’s part of Python’s syntax. The compiler parses the f-string and breaks it into literal parts and expressions to be evaluated. This compiles down to highly efficient bytecode that builds the final string in one fast operation. This is much faster than the .format() method, which must parse the format string at runtime.

Migration Tools

Tools like flynt and pyupgrade can automatically convert old code using .format() and % to modern f-strings, which is a great way to quickly modernize a legacy codebase.

Leave a Reply

Your email address will not be published. Required fields are marked *