Las f-strings son literales de cadena prefijados con f (o F) que permiten incrustar expresiones Python directamente dentro de llaves {}. Python las evalúa en tiempo de ejecución, sustituye cada expresión por su valor y devuelve una cadena nueva. Eso es todo a nivel conceptual, pero los detalles de cómo funcionan internamente explican tanto su eficiencia como sus capacidades.
Cuando el intérprete encuentra f"Hola {nombre}", no construye primero una plantilla y luego la procesa —como hace .format()— sino que el compilador de bytecode transforma la f-string en una serie de operaciones BUILD_STRING que concatenan los fragmentos directamente en el objeto str final. Puedes comprobarlo con dis.dis(lambda: f"Hola {nombre}"). El resultado es que las f-strings son consistentemente más rápidas que % y .format() porque eliminan una capa de parseo en tiempo de ejecución.
Usa f-strings siempre que construyas cadenas con valores dinámicos en código moderno (Python ≥ 3.6). La única excepción razonable es cuando la plantilla tiene que vivir en una variable o configuración externa —ahí .format() sigue siendo la herramienta correcta, porque las f-strings se evalúan en el momento de su definición, no más tarde.
Lo que rompe a la gente nueva: meter una expresión compleja dentro de {} sin entender qué se puede y qué no se puede escribir ahí. Las llaves admiten cualquier expresión Python válida —llamadas a funciones, comprensiones, operadores ternarios— pero no sentencias (if, for, = de asignación). Si intentas asignar dentro de una f-string obtienes un SyntaxError inmediato.
import math
from datetime import datetime
# ── Datos de ejemplo ─────────────────────────────────────────────────────────
pi = math.pi
population = 8_100_000_000
name = " ada lovelace "
items = [10, 20, 30]
today = datetime.now()
# ── 1. Expresiones arbitrarias dentro de {} ───────────────────────────────────
print(f"Área del círculo (r=5): {math.pi * 5 ** 2:.4f}")
print(f"Nombre en título: {name.strip().title()}")
print(f"Suma de items: {sum(items)}")
# Expresión ternaria dentro de la f-string
threshold = 25
print(f"Temperatura: {threshold}°C — {'calor' if threshold > 20 else 'frío'}")
# ── 2. Especificadores de formato ─────────────────────────────────────────────
print(f"Pi con 2 decimales: {pi:.2f}")
print(f"Pi con notación e: {pi:e}")
print(f"Población con comas: {population:,}") # separador de miles
print(f"Población con guiones: {population:_}") # separador alternativo
print(f"Alineado a la derecha: {name.strip():>20}") # ancho 20, alinea derecha
print(f"Alineado a la izquierda:{name.strip():<20}|")
print(f"Centrado con relleno: {'Python':*^20}") # rellena con *
print(f"Entero en binario: {42:08b}") # 8 dígitos, relleno con 0
print(f"Entero en hexadecimal: {255:#x}") # prefijo 0x automático
# ── 3. Operador = para debugging (Python ≥ 3.8) ───────────────────────────────
x = 42
y = [1, 2, 3]
calculated = math.sqrt(144)
print(f"{x=}") # → x=42
print(f"{y=}") # → y=[1, 2, 3]
print(f"{calculated=:.2f}") # = y especificador de formato combinados → calculated=12.00
print(f"{len(items)=}") # funciona con expresiones completas → len(items)=3
print(f"{math.pi * 2=:.4f}") # → math.pi * 2=6.2832
# ── 4. Fecha con formato ──────────────────────────────────────────────────────
print(f"Hoy es: {today:%d/%m/%Y %H:%M}") # directivas strftime dentro del especificador
# ── 5. F-string multilínea ────────────────────────────────────────────────────
report = (
f"{'─' * 35}\n"
f" Reporte: {today:%Y-%m-%d}\n"
f" Población mundial: {population:>15,}\n"
f" Pi aproximado: {pi:>15.6f}\n"
f"{'─' * 35}"
)
print(report)
Desglose del código
El bloque de expresiones arbitrarias muestra el punto más importante: {} acepta cualquier expresión que Python pueda evaluar. name.strip().title() es una cadena de métodos completa. sum(items) es una llamada a función. El operador ternario funciona igual que en cualquier otro contexto. Lo que estás haciendo es exactamente lo que harías en una línea normal de Python; la f-string simplemente captura el resultado.
Los especificadores de formato viven después de los dos puntos dentro de las llaves y usan la misma mini-lenguaje que .format(). :, y :_ son los más útiles para números grandes —uno usa comas al estilo anglosajón, el otro guiones bajos. La sintaxis {valor:relleno}{alineación}{ancho} te da control completo sobre la presentación: {nombre:*^20} significa “centra en un campo de 20 caracteres, rellena con *“. El formato 08b convierte a binario con 8 dígitos y relleno de ceros —muy común cuando trabajas con máscaras de bits.
El operador = (f"{variable=}") es el que más rentabilidad da con menos esfuerzo. Lo que hace internamente es conservar el texto literal de la expresión —no solo su valor— y producir expresión=valor en la salida. Fíjate en f"{calculated=:.2f}": el = y el especificador de formato conviven sin problema, el = captura el nombre y el :2f formatea el número. Esto convierte a las f-strings en una herramienta de debugging rápida que no necesita print("calculated =", calculated) ni nada parecido.
Las f-strings multilínea se consiguen simplemente concatenando literales dentro de paréntesis. Cada línea es una f-string independiente; Python las une en tiempo de compilación. No necesitas \ de continuación ni """ —y con """ perderías la capacidad de identar el código limpiamente sin añadir espacios indeseados a la cadena.
Errores que debes conocer
Error: intentar incluir la misma clase de comillas dentro de la expresión que las que delimitan la f-string —hasta Python 3.11 esto falla con SyntaxError.
# ❌ Wrong (Python < 3.12)
data = {"key": "value"}
print(f"Valor: {data["key"]}") # SyntaxError: comillas dobles anidadas
# ✅ Right
print(f"Valor: {data['key']}") # comillas simples dentro, dobles fuera
Desde Python 3.12 esta restricción desapareció, pero hasta entonces la solución es alternar el tipo de comillas o extraer el valor a una variable antes de la f-string.
Error: asumir que f"{objeto=}" muestra la representación “bonita” —en realidad usa repr(), no str().
# ❌ Wrong (expectativa incorrecta)
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __str__(self):
return f"({self.x}, {self.y})"
p = Point(3, 4)
print(f"{p=}") # → p=<__main__.Point object at 0x...> ← usa repr, no str
# ✅ Right
print(f"{p!s=}") # conversion flag !s fuerza str() → p=(3, 4)
# o bien, define __repr__ en la clase
El flag !s fuerza la conversión con str() antes de aplicar el especificador de formato, que es exactamente lo que necesitas cuando el __repr__ de un objeto no es útil para humanos.
N° 36