[Ultimate Guide] f-strings in Python - Learn By Example (2024)

Python f-strings, formally known as formatted strings, offer a way to embed Python expressions directly within your strings using a minimal syntax.

This feature was introduced in Python 3.6 and has since become a popular method for formatting strings due to its readability, conciseness, and superior performance compared to other formatting tools like the .format() method and the modulo operator %.

By the end of this tutorial, you’ll understand the advantages of f-strings and why they’re an essential tool for any Python developer to master. Let’s get started!

f-string Syntax

An f-string looks like:

f’..<text>..{ <expression> <conversion> : <format specifier> }..<text>..’

Let’s break down the syntax:


An f-string always begins with the letter ‘f’ or ‘F’ before the opening quotation mark. This tells Python to treat the string as an f-string.

Literal Text

This is the static part of your f-string. It appears as-is in your final string.

Curly Braces { }

Curly braces enclose the dynamic parts of your f-string—expressions, conversion flags, and format specifiers. Anything you place inside these braces will be evaluated at runtime, and the result will be inserted into your string.


An expression can be any valid Python expression. This includes variable names, arithmetic operations, function calls, and more.

Conversion (Optional)

After the expression, you can optionally include a conversion flag preceded by an exclamation mark !. This allows you to apply a specific type conversion before formatting. There are three conversion flags:

  • !s calls str() on the expression
  • !r calls repr() on the expression
  • !a calls ascii() on the expression

Format Specifiers (Optional)

Following the expression (and optional conversion), you can include a colon : and then a format specifier. Format specifiers let you control how the expression’s result should be formatted, such as alignment, padding, decimal precision, and more.

Here are all the formatting options:

Formatting optionsValid values
fill<any character>
align< | > | = | ^
sign+ | |
width<any number>
group_ | ,
precision<any number>
typeb | c | d | e | E | f | F | g | G | n | o | s | x | X | %

Let’s see f-strings in action:

Basic Formatting

The most straightforward use of f-strings is to embed variables within a string. To get started, simply prefix your string with the letter ‘f’ or ‘F’. Inside the string, use curly braces {} to mark the spots where you want to insert the values of variables.

For example, if you have variables name and age defined, you could create a string like this:

name = "Bob"age = 30print(f"Hello, my name is {name} and I'm {age} years old.")# Output: Hello, my name is Bob and I'm 30 years old.

It’s important to note that Python evaluates f-strings at runtime. This means that when your code runs, Python will replace the placeholders within curly braces with the actual values of your variables or expressions. For this to work, you need to make sure the variables you’re using in the f-string have been defined beforehand and are currently in scope.

Embedding Expressions

Within the curly braces, you can include any valid Python expression. These can be:

  • Variables
  • Arithmetic operations
  • Function calls
  • Object attributes
  • …almost any expression you’d use in regular Python code

Let’s illustrate with examples:

Arithmetic operations

f-strings aren’t just limited to embedding variables; you can perform arithmetic operations directly within the curly braces.

a = 5b = 10print(f"Five plus ten is {a + b} and five times ten is {a * b}.")# Output: Five plus ten is 15 and five times ten is 50.

Accessing List and Dictionary Items

You can also insert values from data structures like lists and dictionaries directly into your strings.

Suppose you have a list named colors. To include a specific color in your string, you can reference its index within the f-string’s curly braces:

colors = ['red', 'green', 'blue']print(f"My favorite color is {colors[2]}.")# Output: My favorite color is blue.

Similarly, you can access elements from a dictionary by their corresponding keys. If you have a dictionary named person, you could create a formatted string like this:

person = {'name': 'Bob', 'age': 30}print(f"{person['name']} is {person['age']} years old.")# Output: Bob is 30 years old.

Inline if-else within an f-string

f-strings support inline if-else expressions, allowing you to easily embed conditional logic directly into your strings.

For example, let’s say you have a variable temperature. You can write an f-string to print whether it’s warm or cold:

temperature = 20print(f"It's {'warm' if temperature > 15 else 'cold'} today.")# Output: It's warm today.

Accessing Object Attributes

When you work with custom classes and objects in Python, f-strings let you directly access and display their attributes.

For example, if you have a Student object, you can create a string that readily incorporates the student’s name and score information.

class Student: def __init__(self, name, score): self.name = name self.score = scorestudent1 = Student("Bob", 95)print(f"{student1.name} achieved a score of {student1.score} on the math test.") # Output: Bob achieved a score of 95 on the math test.

Calling functions

You can call functions directly within an f-string. This helps you avoid the need to store the return value of the function in an intermediate variable, making your code more concise.

def calculate_area(length, width): return length * widthprint(f"The area of the rectangle is {calculate_area(10, 5)}.") # Output: The area of the rectangle is 50.

Escaping braces

In cases where you need to include literal curly braces in your string, you can do so by doubling them. This tells Python to treat them as regular characters rather than part of the f-string syntax.

print(f"Programmers love {{curly braces}}!")# Output: Programmers love {curly braces}!

Aligning, Padding and Truncating

f-strings support aligning, padding and truncating. This is particularly useful for creating neatly formatted outputs, especially in tabular data or reports.


To control alignment within an f-string, you’ll use an alignment specifier and a width value within the curly braces. The format is :<align> <width>

The various alignment options are:

<Align Left
>Align Right (default for numbers).
=Add padding between sign & digits. (valid for numbers only)
^Align Center

Here are some examples:

# Align text leftprint(f"{'Left':<12}")# Output: Left        
# Align text rightprint(f"{'Right':>12}")# Output:        Right
# Align text centerprint(f"{'Center':^12}")# Output:    Center   

When you don’t use the alignment specifier, by default, strings are left-aligned, and numbers are right-aligned:

print(f"{'Text':12}")# Output: Text        print(f"{42:12}")# Output:           42

When using center alignment, if the length of the string results in an uneven split of padding characters, the extra character will be placed on the right side.

# Align text centerprint(f"{'Center':^11}")# Output:   Center   


By default, f-strings pad with spaces. You can customize this by specifying a fill character after the colon and before the alignment symbol:

# Choose custom fill characterprint(f"{'Center':*^12}")# Output: ***Center***


You can truncate long strings by adding a precision specifier within the curly braces, using the following format: :. <number>

For instance, to truncate the string “Python” to its first two characters:

# Truncate string to two charactersprint(f"{'Python':.2}")# Output: Py

You can combine truncation with other formatting options, such as padding and alignment. To center a truncated string within a 10-character space:

# Add padding to a truncated string and align it centerprint(f"{'Python':^10.2}")# Output:     Py    

Format Integers

Python provides a variety of type codes to customize the formatting of integers within f-strings.

bBinary format
cCorresponding unicode character
dDecimal Integer
oOctal format
xHex format (lowercase letters)
XSame as ‘x’, but uses uppercase letters
nNumber (Same as ‘d’, except it uses current locale setting for number separator)

Here are some examples:

# Convert a number to hex, octal, binary and unicode characternum = 42print(f"int:{num:d}, hex:{num:x}, oct:{num:o}, bin:{num:b}, char:{num:c}")# Output: int:42, hex:2a, oct:52, bin:101010, char:*

To include prefixes like ‘0x’ (hexadecimal), ‘0o’ (octal), and ‘0b’ (binary), use the ‘#’ formatting option:

# Add a prefix to Hex, Octal and Binarynum = 42print(f"hex:{num:#x}, oct:{num:#o}, bin:{num:#b}")# Output: hex:0x2a, oct:0o52, bin:0b101010

Format Floating Point Numbers

When working with floating-point numbers, you can achieve a variety of formatting effects by specifying left justification, zero padding, numeric signs, total field width, and the number of digits after the decimal point.

Following type codes are used to format floating-point numbers:

eExponent notation (lowercase)
ESame as ‘e’, but uses uppercase letters
fFloating-point decimal (default precision: 6)
FSame as ‘f’, but uses uppercase letters
gGeneral format. Round numbers (default precision: 6)
GSame as ‘g’ (shows exponent for large numbers)
%Percentage (multiplies by 100 & adds % sign)
# Show floating point numberprint(f"{3.141592653:f}")# Output: 3.141593

By default, f-strings round floating-point numbers to 6 decimal places.

If you want to limit the number of digits after the decimal point, specify the precision option (e.g., .2f for two decimal places).

# Specify digits after the decimal point (Precision)print(f"{3.141592653:.2f}")# Output: 3.14

To display numbers in scientific (exponential) notation, use type code ‘e‘ or ‘E‘ (for uppercase letter)

# Display numbers with exponent notationprint(f"{3.141592653:.2e}")# Output: 3.14e+00

You can format numbers as percentages using the % type code. This multiplies the number by 100 and appends a percent sign at the end.

# Format number as percentageprint(f"{19.5/22:.2%}")# Output: 88.64%

Signed Numbers

By default, f-strings only display a minus sign - in front of negative numbers. However, you can control how the signs of both positive and negative numbers are displayed using the following sign formatting options:

+Displays sign for both positive and negative numbers
Displays sign only for negative numbers
spaceDisplays space for positive numbers and a minus sign for negative numbers

Here are some examples:

# Display sign for both positive and negative numbersprint(f"{3.14:+.2f}, {-3.14:+.2f}")# Output: +3.14, -3.14
# Display sign only for negative numbersprint(f"{3.14:-.2f}, {-3.14:-.2f}")# Output: 3.14, -3.14

By default negative numbers are prefixed with a sign, so {:-f} is same as {:f}

When you use ' ' (space) for the sign option, it displays a leading space for positive numbers and a minus sign for negative numbers.

# Display a space for positive numbersprint(f"{3.14: .2f}, {-3.14: .2f}")# Output:  3.14, -3.14

Padding Numbers (Intergers & Floats)

Just like you can format strings, f-strings let you add padding and spacing around numbers.

# Add padding to a numberprint(f"{42:5d}")# Output:    42

By specifying a field width (the ‘5’ in this example), you create space around the number. By default, spaces are added to the left.

To pad with zeros instead of spaces, use 0 as the fill character and the > option for right alignment.

# Padding zeros to a numberprint(f"{7:0>3d}")# Output: 007

With floating-point numbers, the padding value represents the total width of the output, including the decimal point and digits.

# Padding zeros to a floating pointprint(f"{3.141592653589793:06.2f}")# Output: 003.14

To add padding between the sign and digits, you use the = alignment option.

# Padding zeros to a positive numberprint(f"{120:0=+8d}")# Output: +0000120

Thousands Separator and Nibble Separator

f-strings offer a convenient way to insert commas as thousands separators, which is especially helpful in financial or statistical outputs.

To achieve this, simply include a comma , as a group formatting option within the format specifier of your f-string:

# Using the comma as a thousands separatorprint(f"{1234567890:,}")# Output: 1,234,567,890

Additionally, for binary, octal, and hexadecimal numbers, you can use an underscore _ to separate nibbles (groups of 4 digits):

# Using underscore as a nibble separatorprint(f"{0b01010101010:_b}")# Output: 10_1010_1010

Date and Time Formatting

Python’s built-in datetime module is one of the most important modules for handling dates and times. By combining f-strings with the strftime() method‘s formatting codes, you can customize how date and time information is displayed. Whether you need a simple month-day-year format or a more detailed representation, f-strings offer the necessary flexibility.

Let’s see an example:

from datetime import datetimeformatted_date = f"Today's date is {datetime.now():%B %d, %Y}"print(formatted_date)# Output: Today's date is March 01, 2024

In this example, we start by importing the datetime module. The datetime.now() function gets the current date and time. Inside the f-string, we use strftime-style format codes to control how the date is displayed:

  • %B represents the full month name
  • %d is the day of the month
  • %Y gives the year in four-digit format

Parametrized Formats

Python allows formatting options to be specified dynamically using Parameterization. This technique allows you to easily change your formatting behavior at runtime without hardcoding the formatting specifications directly into the f-string.

To use this feature, you first define variables to store your desired formatting choices. Then, within the f-string, you embed these variables into the format specifier using additional curly braces {:{}}.

# Parametrized fill, alignment and widthfill="*"align="^"width="12"print(f"{'center':{fill}{align}{width}}")# Output: ***center***

By changing the values of fill, align, and width, you can easily modify how your content is formatted without altering the f-string itself.

Concatenating strings

In Python, when you place string literals right next to each other in your code, the Python compiler automatically joins them together into a single string. This process is called Implicit Concatenation.

Implicit concatenation applies to both regular strings and f-strings. However, there’s a subtle difference in when the concatenation occurs:

  • Regular strings: The compiler joins them together at compile time, before your code even runs.
  • f-strings: The concatenation happens at runtime, as your code is executing. This is because f-strings need to evaluate any expressions within the curly braces.

Let’s see an example:

name = "Bob"print(f"Hello, my name is {name}, " "and I'm a Software Engineer.")# Output: Hello, my name is Bob, and I'm a Software Engineer.

Evaluation order of expressions

The expressions in an f-string are evaluated in left-to-right order. While this order of evaluation might not always be obvious, it becomes apparent when your expressions have side effects (like modifying variables or calling functions that change something outside their scope).

Let’s look at an example:

# Define a global variablecount = 0# A simple function that increments the global variabledef add_one(): global count count += 1 return count# In an f-string, expressions are evaluated from left to right.# Each call to add_one has a side effect of incrementing the global variablemessage = f"The count starts at {add_one()}, then goes to {add_one()}, and finally reaches {add_one()}."print(message)# Output: The count starts at 1, then goes to 2, and finally reaches 3.

In this example, each time the add_one() function is called within the f-string, it increments the global variable count by 1 and returns the new value. Since f-string expressions are evaluated sequentially from left to right, you can observe the count increasing with each function call within the same f-string.

Custom format specifiers

f-strings use the __format__ method of objects to apply format specifiers. When a format specifier is encountered within an f-string, Python searches for the __format__ method associated with the object. This method determines how the object should be formatted and displayed.

By overriding the __format__ method within a class, you can control how objects of that class are formatted. This allows you to customize the formatting of your custom objects within f-strings.

Below is a simple example where we override the __format__ method of a class that represents a temperature reading. Depending on the format specifier provided, it will return the temperature in Celsius or Fahrenheit.

class Temperature: def __init__(self, celsius): self.celsius = celsius # Custom formatting of the temperature object based on the format specifier def __format__(self, fmt): if fmt == "F": # Convert to Fahrenheit and format the string with 1 decimal place return f"{(self.celsius * 9 / 5) + 32:.1f}°F" # Default to Celsius if no format or an unrecognized format is specified return f"{self.celsius:.1f}°C"# Create a temperature object with a given value in Celsiustemperature = Temperature(25)# Print the temperature in default Celsius formatprint(f"{temperature}") # Output: 25.0°C# Print the temperature in Fahrenheit using the custom format specifier 'F'print(f"{temperature:F}") # Output: 77.0°F

In this example, the Temperature class has a __format__ method. This method is automatically called when you use a Temperature object inside an f-string. The fmt argument within this method receives the format specifier you specify, such as ‘F’. Based on this specifier, the method calculates and returns the appropriate string representation.

If the specifier is “F”, the method converts the temperature to Fahrenheit and formats the output to display one decimal place followed by °F. If no format specifier is provided or if it’s unrecognized, the temperature is displayed in Celsius by default.

This functionality allows us to easily format and display the temperature in different units directly within an f-string.

Using an Object’s String Representations in f-strings

When you work with objects in Python (whether it’s a string, a number, a list, or a custom class you created), the way they appear when you print them isn’t accidental. Python relies on two special methods:

  • __str__(): This method is responsible for producing a user-friendly, readable string representation of your object.
  • __repr__(): This method aims to provide a developer-friendly representation of the object. Often, it includes details about the object’s type and the values of its attributes.

f-strings, with the help of the !s and !r conversion flags, allow you to choose which of these representations you want to use within your formatted strings:

  • !s calls the __str__ method of the object.
  • !r calls the __repr__ method of the object.

Let’s see how to use !s and !r conversion flags with a Person class example:

class Person: def __init__(self, name, age): self.name = name self.age = age def __str__(self): return f"I'm {self.name}, and I'm {self.age} years old." def __repr__(self): return f"{type(self).__name__}(name='{self.name}', age={self.age})"

This class has two instance attributes: .name and .age, along with two methods: __str__ and __repr__. The __str__ method returns a string that includes the person’s name and age in a friendly message, whereas the __repr__ method provides a more technical representation of the person object suitable for developers.

When you use !s within an f-string, Python calls the __str__ method of the Person object; similarly, when you use !r, Python calls the __repr__ method.

bob = Person("Bob", 30)print(f"{bob!s}") # Output: I'm Bob, and I'm 30 years old.print(f"{bob!r}") # Output: Person(name='Bob', age=30)

Self-Documenting Expressions for Debugging

f-strings offer a handy feature that can be useful, especially during your debugging process.

By placing a variable name followed by an equal sign = within an f-string, you create a self-documented expression that automatically includes: the variable’s name, an equal sign and the variable’s current value.

Here’s how it works:

num = 42print(f"{num = }")# Output: num = 42

This feature allows you to quickly insert self-documenting expressions to track how variables change throughout your code’s execution.

Using Quotation Marks

Python supports the use of both single quotes ' and double quotes " to define string literals. You can also use triple versions of these (''' or """), which are mainly helpful for multiline strings. All of these work seamlessly with f-strings.

Earlier versions of Python had a limitation within f-strings. You couldn’t use the same type of quotation mark for both the outer string and for values embedded within it. This often raises a SyntaxError exception.

Thankfully, Python 3.12 fixed this! Now, you can freely reuse quotation marks within your f-strings without facing syntax errors.

# Python 3.12+person = {"name": "Bob", "age": 30}print(f"Hello, I'm {person["name"]}.")# Output: Hello, I'm Bob.

Using Backslashes

Before Python 3.12, another limitation of f-strings was that you couldn’t use backslashes within the embedded expressions. This created issues when attempting to include escape sequences, such as \n (newline) or \t (tab), for formatting purposes.

Thankfully, Python 3.12 addressed this limitation. You can now freely use backslashes and escape sequences within your f-string expressions.

# Python 3.12+words = ["Hello", "World!"]print(f"{'\n'.join(words)}")# Output: # Hello# World!

f-strings in Python versions before 3.12 had another restriction—you couldn’t include the # symbol within embedded expressions, which prevented you from adding inline comments.

Thankfully, Python 3.12 addressed this! You can now add comments using # within your f-string expressions to provide explanations and context. Another improvement allows you to insert line breaks within the curly braces, enabling you to split long f-string expressions across multiple lines for enhanced readability.

# Python 3.12+temperature = 20print(f"It's { 'warm' if temperature > 15 else 'cold' # Check if temperature is above 15 degrees } today.")# Output: It's warm today.

Multi-line F-strings

F-strings, like regular strings in Python, support the use of triple quotes (either ''' or """). This allows you to create f-strings that span multiple lines, making them incredibly handy for constructing longer or more complex formatted strings.

name = "Bob"multiline_string = f"""Hello, {name}!It's great to see you here.How have you been?"""print(multiline_string)# Output: # Hello, Bob!# It's great to see you here.# How have you been?

Raw f-strings (Raw Formatted Strings)

You can also create raw f-strings by combining the f and r prefixes (e.g., rf"some_text{expression}"). This allows you to enjoy the benefits of both raw strings and f-strings, particularly useful when you need to include expressions within strings that contain many literal backslashes.

file_name = "data.txt"path = rf"C:\Users\Documents\{file_name}"print(path)# Output: C:\Users\Documents\data.txt

Using Lambdas in f-string

f-strings rely on the colon : character to signal the start of a format specifier within an expression. This creates a conflict when you try to directly include a lambda function (which also uses a colon to define its parameters) within an f-string.

The solution is to use lambdas within f-strings, enclose them in parentheses.

print(f"Result: {(lambda x: x * 2)(5)}")# Result: 10

Comparing Performance: F-String vs .format() vs modulo operator (%)

f-strings aren’t just about readability; they also offer a slight performance edge over older string formatting methods like the modulo operator % and the .format() method.

In the code below, the timeit module is used to measure the execution time for building a string with these three methods. Let’s see how this plays out:

import timeitname = "John Doe"age = 30strings = { "f_string": "f'Name: {name} Age: {age}'", "Modulo operator": "'Name: %s Age: %s' % (name, age)", ".format() method": "'Name: {} Age: {}'.format(name, age)",}def perf_test(strings): max_length = len(max(strings, key=len)) for tool, string in strings.items(): time = timeit.timeit( string, number=1000000, globals=globals() ) * 1000 print(f"{tool}: {time:>{max_length - len(tool) + 6}.2f} ms")perf_test(strings)

In this code, we define different string formatting methods within the strings dictionary. The run_performance_test function uses timeit.timeit to repeatedly execute each formatting method one million times. It then calculates and prints the execution time in milliseconds for each method.

You’ll likely see an output similar to the following (though your exact times might differ slightly):

f_string: 122.94 msModulo operator: 134.52 ms.format() method: 247.27 ms

This performance test shows that f-strings are a bit faster than the % operator and the .format() method. This makes f-strings the clear winner when it comes to readability, conciseness, and performance.

So, the next time you need to format strings in your Python code, use f-strings—they’re the best tool for the job!

[Ultimate Guide] f-strings in Python - Learn By Example (2024)
Top Articles
Latest Posts
Article information

Author: Reed Wilderman

Last Updated:

Views: 5874

Rating: 4.1 / 5 (52 voted)

Reviews: 91% of readers found this page helpful

Author information

Name: Reed Wilderman

Birthday: 1992-06-14

Address: 998 Estell Village, Lake Oscarberg, SD 48713-6877

Phone: +21813267449721

Job: Technology Engineer

Hobby: Swimming, Do it yourself, Beekeeping, Lapidary, Cosplaying, Hiking, Graffiti

Introduction: My name is Reed Wilderman, I am a faithful, bright, lucky, adventurous, lively, rich, vast person who loves writing and wants to share my knowledge and understanding with you.