f-strings: formato completo y debugging con el operador =

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.

36

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio