¿Cómo posponer/diferir la evaluación de las cuerdas f?
Estoy usando cadenas de plantilla 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 desde 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 la interprete como una nueva cadena f, pero no sé si existe tal cosa.
¿Hay alguna forma de introducir una cadena e interpretarla como una cadena f para evitar el uso de la .format(**locals())
llamada?
Lo ideal sería 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 este resultado deseado (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}
Una forma concisa de evaluar una cadena como una cadena f (con todas sus capacidades) es utilizar 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 bar
Y, a diferencia de muchas otras soluciones propuestas, también puedes 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 BARBAR
Aquí tienes un "Ideal 2" completo.
No es una cadena f (ni siquiera usa cadenas f), pero hace lo que se le solicita. Sintaxis exactamente como se especifica. Sin dolores de cabeza en seguridad ya que no estamos usando eval()
.
Utiliza una pequeña clase e implementos __str__
que se llaman automáticamente mediante impresión. Para escapar del alcance limitado de la clase, usamos el inspect
módulo para saltar un cuadro 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 bar