Estoy usando cadenas de plantillas para generar algunos archivos y me encanta la concisión de las nuevas cadenas f para este propósito, para reducir mi código de plantilla anterior de algo como esto:
template_a = "The current name is {name}" names = ["foo", "bar"] for name in names: print (template_a.format(**locals()))Ahora puedo hacer esto, reemplazando directamente las variables:
names = ["foo", "bar"] for name in names: print (f"The current name is {name}")Sin embargo, a veces tiene sentido tener la plantilla definida en otro lugar: más arriba en el código, o importada de un archivo o algo así. Esto significa que la plantilla es una cadena estática con etiquetas de formato. Algo tendría que pasarle a la cadena para decirle al intérprete que interprete la cadena como una nueva cadena f, pero no sé si existe tal cosa.
¿Hay alguna forma de traer una cadena y hacer que se interprete como una cadena f para evitar usar la .format(**locals()) ?
Idealmente, quiero poder codificar así... (donde magic_fstring_function es donde entra la parte que no entiendo):
template_a = f"The current name is {name}" # OR [Ideal2] template_a = magic_fstring_function(open('template.txt').read()) names = ["foo", "bar"] for name in names: print (template_a)... con esta salida deseada (sin leer el archivo dos veces):
The current name is foo The current name is bar... pero el resultado real que obtengo es:
The current name is {name} The current name is {name}Aquí tienes un "Ideal 2" completo.
No es un f-string, ni siquiera usa f-strings, pero hace lo solicitado. Sintaxis exactamente como se especifica. No hay dolores de cabeza de seguridad ya que no estamos usando eval() .
Utiliza una pequeña clase e implementa __str__ , que se llama automáticamente por impresión. Para escapar del alcance limitado de la clase, usamos el módulo de inspect para saltar un cuadro hacia arriba y ver las variables a las que tiene acceso la persona que llama.
import inspect class magic_fstring_function: def __init__(self, payload): self.payload = payload def __str__(self): vars = inspect.currentframe().f_back.f_globals.copy() vars.update(inspect.currentframe().f_back.f_locals) return self.payload.format(**vars) template = "The current name is {name}" template_a = magic_fstring_function(template) # use it inside a function to demonstrate it gets the scoping right def new_scope(): names = ["foo", "bar"] for name in names: print(template_a) new_scope() # The current name is foo # The current name is barUna forma concisa de evaluar una cadena como una cadena f (con todas sus capacidades) es usar la siguiente función:
def fstr(template): return eval(f"f'{template}'")Entonces puedes hacer:
template_a = "The current name is {name}" names = ["foo", "bar"] for name in names: print(fstr(template_a)) # The current name is foo # The current name is barY, a diferencia de muchas otras soluciones propuestas, también puede hacer:
template_b = "The current name is {name.upper() * 2}" for name in names: print(fstr(template_b)) # The current name is FOOFOO # The current name is BARBAREsto significa que la plantilla es una cadena estática con etiquetas de formato.
Sí, esa es exactamente la razón por la que tenemos literales con campos de reemplazo y .format , para que podamos reemplazar los campos cuando queramos llamando a format en él.
Algo tendría que pasarle a la cadena para decirle al intérprete que interprete la cadena como una nueva cadena f
Ese es el prefijo f/F . Podría envolverlo en una función y posponer la evaluación durante el tiempo de la llamada, pero por supuesto eso genera una sobrecarga adicional:
template_a = lambda: f"The current name is {name}" names = ["foo", "bar"] for name in names: print (template_a())Que imprime:
The current name is foo The current name is barpero se siente mal y está limitado por el hecho de que solo puede mirar el espacio de nombres global en sus reemplazos. Intentar usarlo en una situación que requiere nombres locales fallará miserablemente a menos que se pase a la cadena como argumentos (lo que supera totalmente el punto).
¿Hay alguna forma de traer una cadena y hacer que se interprete como una cadena f para evitar usar la
.format(**locals())?
Aparte de una función (limitaciones incluidas), no, también podría quedarse con .format .