¿Cómo posponer/diferir la evaluación de las cuerdas f?

Resuelto JDAnders asked hace 7 años • 15 respuestas

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_functiones 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}
JDAnders avatar Feb 28 '17 06:02 JDAnders
Aceptado

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
kadee avatar Dec 07 '2018 14:12 kadee

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 inspectmó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
Paul Panzer avatar Feb 27 '2017 23:02 Paul Panzer