Modern Strings in Python
By: Cam Wohlfeil
Published: 2018-07-10 0000 EDT
Category: Programming
Tags:
python
I'll be honest, when I first read about f-strings (aka formatted string literals) coming to Python, I was not impressed. The syntax looked pretty bad and it was creating yet another way to do the same thing, which goes against the Zen of Python. After going over it a bit more they have grown on me, especially considering they are faster and less error prone.
Before Python 3.6, there were two ways to format strings, %-formatting and str.format(). %-formatting is the C-inspired 'legacy' method:
# %-formatting
>>> name = "Earth"
>>> age = "4.543 billion"
>>> "Hello, %s. You are %s years old." % (name, age)
'Hello Earth. You are 4.543 billion years old.'
%-formatting is no longer recommended because it's highly error prone and has issues displaying dictionaries and tuples, on top of being ugly and unreadable with more than a few pieces of data. Ever since getting serious about Python, and especially python 3, I've mainly used str.format() because it's very similar to what I'm used to with other languages and was much nicer than %-formatting. It also has a lot of nifty features:
# str.format()
>>> "Hello, {}. You are {}.".format(name, age)
'Hello Earth. You are 4.543 billion years old.'
# Alternatively
>>> "Hello, {1}. You are {0}.".format(name, age)
'Hello 4.543 billion. You are Earth years old.'
# You can even pass parameters
>>> person = {'name': 'Earth', 'age': '4.543 billion'}
>>> "Hello, {name}. You are {age} years old.".format(name=person['name'], age=person['age'])
'Hello, Earth. You are 4.543 billion years old.'
# With dictionaries, you can even use ** to pass both the key and value
>>> "Hello, {name}. You are {age} years old.".format(**person)
'Hello, Earth. You are 4.543 billion years old.'
That being said, str.format still gets a bit unwieldy with multiple parameters and longer strings. With Python 3.6, we now have f-strings to help, at least until we find the issue with these and something else replaces them:
# Lowercase or capital 'f' are both acceptable for f-string identifier
>>> f"Hello, {name}. You are {age}."
'Hello Earth. You are 4.543 billion years old.'
The basic syntax is pretty similar to str.format(), just less verbose and with the f-string identifier. But wait, there's more! f-strings are evaluated at runtime meaning you can put any valid expression within them:
# Basic example
>>> f"{2 * 37}"
'74'
# Function call
>>> def to_lowercase(input):
... return input.lower()
>>> f"{to_lowercase(name)} is old."
'earth is old.'
# Method call
>>> f"{name.lower()} is old."
'earth is old.'
# You can even use objects!
class Planet:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f"{self.name} is {self.age} years old."
def __repr__(self):
return f"The planet {self.name} is {self.age} years old. Wow!"
# Initialize the object, obviously
>>> new_planet = Planet("Earth", "Idle", "4.543 billion")
# Return the string dunder method, the informal string representation of the object
>>> f"{new_planet}"
'Earth is 4.543 billion years old.'
# Return the representation dunder method, the official representation of the object which should be unambiguous
>>> f"{new_planet!r}"
'Earth is 4.543 billion years old.'
# Multi-line like so
>>> classification = "Gaia planet"
>>> solar_system = "Sol"
>>> message = (
... f"Hello {name}. "
... f"You are a {classification}. "
... f"In the {solar_system} system."
... )
>>> message
'Hello Earth. You are a Gaia planet. In the Sol system.'
# Not like so
>>> message = (
... f"Hi {name}. "
... "You are a {classification}. "
... "You were in {solar_system}."
... )
>>> message
'Hello Earth. You are a {classification}. In the {solar_system} system.'
# Also acceptable
>>> message = f"Hello {name}. " \
... f"You are a {classification}. " \
... f"In the {solar_system} system."
# As with all Python strings, you can use quotes within quotes, just follow the normal rules
# Watch out for triple quotes and multi-line
>>> message = f"""
... Hello {name}.
... You are a {classification}.
... In the {solar_system} system.
... """
...
>>> message
'\n Hello Earth.\n You are a Gaia planet.\n In the Sol system.\n'
# Be careful with quotations and dictionaries
# This will work
>>> comedian = {'name': 'Eric Idle', 'age': 74}
>>> f"The comedian is {comedian['name']}, aged {comedian['age']}."
'The comedian is Eric Idle, aged 74.'
# This will fail
>>> comedian = {'name': 'Eric Idle', 'age': 74}
>>> f'The comedian is {comedian['name']}, aged {comedian['age']}.'
File "<stdin>", line 1
f'The comedian is {comedian['name']}, aged {comedian['age']}.'
^
SyntaxError: invalid syntax
# To use braces in a string, use double braces
>>> f"{{42}}"
'{ 42 }'
# Backslashes work as usual, however they cannot be used in the f-string expression. Just evaluate it beforehand.
>>> f"{\"Alex C. Wolff\"}"
File "<stdin>", line 1
f"{\"Alex C. Wolff\"}"
^
SyntaxError: f-string expression part cannot include a backslash
>>> name = "Alex C. Wolff"
>>> f"{name}"
'Alex C. Wolff'
# f-string expressions do not support comments using the hash (#)
>>> f"Alex is {2 * 30 #So old...}."
File "<stdin>", line 1
f"Alex is {2 * 30 #So old...}."
^
SyntaxError: f-string expression part cannot include '#'
Just like If you want to learn more about how they run under the hood, check my references. In benchmarks f-strings crush str.format() and easily beat the old %-formatting. This is probably mostly due to lower overhead in the former case and no legacy cruft in the latter case.
Since I abandon old projects and only focus on the latest version of Python for my projects, going forward I'll be sticking with f-strings. This way I can retain the Zen of Python of only doing things one way.
References: