When you first encounter f strings in Python, they can seem like a dark art. And you can use them without understanding them. I’ve used f strings to get myself out of a bind, based on a code snippet I found on Stack Overflow or some other discussion board, with a comment saying not to ask me why it works. But here’s what they do and how they can help your code.

What is an f string?

f strings in Python

While f strings can seem a bit cryptic at first, they make Python much more powerful, and if used carefully, less convoluted and confusing as well.

F strings aren’t unique to Python, and they make more sense if you’ve used other languages. I understand they exist in Java, and, having programmed a little in straight C in the bad old days before I figured out I preferred writing for humans than machines, I can even see their roots in C and its printf() function.

The main thing an f string allows you to do is inject other variables into a string. Frequently when doing API work, you have to combine data from multiple sources to build what you need. It can be a hybrid piece of data, or, if you’re really ambitious, a piece of JSON.

A lot of my Python code can be best described as convoluted concatenations. Using f strings gives me a way to clean that up.

Building JSON with an f string and no convoluted concatenations

I recently had to build an integration between a security scanner and an aggregation tool called Nucleus. Both tools speak JSON, but they don’t understand each others’ JSON schema. So I had to convert it. I extracted a bunch of key variables from the scanner data, then used an f string to build some reasonably clean JSON.

To make an f string, assign a variable with a lowercase f followed by a string in quotes. Whenever you want to inject a variable, place the Python variable in curly braces where you need it to appear.

Let’s start with a simple example. Let’s say I have an operating system and version defined in a variable. Contrived in this example I know, but not so much when you’re reading it from a data stream of some kind.

os = 'Windows'
os_version = '95'
output = f'{os} {os_version}'

The contents of the variable output will be “Windows 95” — the two strings separated by a space. In this example, it’s not terribly useful, but you can build onto some impressive things with it. Here’s a more ambitious example, where I build some JSON and inject pre-existing variables.

output = f'{{"import_version": "1", "scan_tool": "Notme", "scan_type": "Container Image", "assets": [{{"host_name": "{registry}/{imageName}:{tag}", "image_repo": "{imageName}", "image_registry": "{registry}", "operating_system_name": "{os} {os_version}", "operating_system_version": "{os_version}", "image_manifest_digest": "{digest}", "image_tags": "{tag}", "asset_info": "notme.image_digest:{digest};notme.registry:{registry};notme.repository:{repository}", "findings": [{{"finding_exploitable": "false", "finding_type": "Vuln", "finding_result": "Failed", "finding_number": "None", "finding_cvss": 0, "finding_severity": "Informational", "finding_description": "No findings in most recent scan.", "finding_name": "No findings in most recent scan"}}]}}]}}'

Without an f string, I would have had a mess of substrings, plus signs, str functions, and other monstrosities. It would have been a nightmare to ever have to go back and revise it. I’ve made code like that work, but whenever someone asks a question afterward, I’m not happy.

With this, all I had to do was build some valid JSON, use a JSON validator to ensure it was good, then inject my variables. Then I knew my code would produce valid JSON. And if I ever need to go back and change a key value pair or add another one, I can easily do it.

I can inject more than variables too. I can inject math if I need to, and even call functions.

When I had the privilege to help demonstrate the Nucleus product to one of my security heroes, I couldn’t understand why he said he wanted to use JSON to send us data. Now that I know f strings, I see why he was so enamored with JSON. He’s one of my security heroes because he’s a few steps ahead of me.

Curly braces and escapes

You can escape reserved characters with the backslash character, just like you do in regular Python strings. And when you need to use curly braces, such as when creating JSON data, just double up the curly braces. Instead of placing { in the string, use {{. You can see in the example above where I did just that. The result is still as readable or convoluted as you want your JSON to be, and easy to maintain as long as you understand the JSON.

In my case, the format of the JSON isn’t terribly important, just knowing where to inject the data is. There are 12 pieces of data I care about; the JSON is just a means to get that data into Nucleus so a client can put that data to work. But if you want yours to look nice, you can always pretty print the JSON using another one of my tricks, then build your code around that.