python - print - ¿Puedo posponer la evaluación de f-strings?
string f python (6)
Esto significa que la plantilla es una cadena estática con etiquetas de formato en ella
Sí, esa es exactamente la razón por la que tenemos literales con campos de reemplazo y .format
, por lo que podemos reemplazar los campos siempre que lo .format
llamando al format
.
Algo debería sucederle 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 implica una sobrecarga adicional:
template_a = lambda: f"The current name is {name}"
names = ["foo", "bar"]
for name in names:
print (template_a())
Que se imprime:
The current name is foo
The current name is bar
pero se siente mal y está limitado por el hecho de que solo puede echar un vistazo al espacio de nombres global en sus reemplazos. Tratar de usarlo en una situación que requiere nombres locales fallará miserablemente a menos que se pase a la cadena como argumentos (que supera totalmente el punto).
¿Hay alguna forma de introducir una cadena y hacer que se interprete como una cadena f para evitar el uso de la
.format(**locals())
?
Aparte de una función (limitaciones incluidas), no, así que también podría seguir con .format
.
Estoy utilizando cadenas de plantillas para generar algunos archivos y me encanta la concisión de las nuevas cadenas para este propósito.
Anteriormente haría 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:
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. Esto significa que la plantilla es una cadena estática con etiquetas de formato en ella. Algo debería sucederle a la cadena para decirle al intérprete que interprete la cadena como una nueva cadena de caracteres, pero no sé si existe tal cosa.
¿Hay alguna forma de introducir una cadena y hacer que se interprete como una cadena f para evitar el uso de la .format(**locals())
?
Ideal:
template_a = f"The current name is {name}"
names = ["foo", "bar"]
for name in names:
print (template_a)
Salida deseada:
The current name is foo
The current name is bar
Salida real:
The current name is {name}
The current name is {name}
Editar:
Ideal 2:
Los contenidos de template.txt son The current name is {name}
. La función magic_fstring_function
es donde magic_fstring_function
la parte que no entiendo.
template_a = magic_fstring_function(open(''template.txt'').read())
names = ["foo", "bar"]
for name in names:
print (template_a)
Salida deseada (sin leer el archivo dos veces):
The current name is foo
The current name is bar
Salida real:
The current name is {name}
The current name is {name}
Aquí hay un completo "Ideal 2".
No es una f-string que ni siquiera usa f-strings. Pero hace lo que se le pide. 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 y ver las variables a las que la persona que llama tiene acceso.
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
O tal vez no use f-strings, simplemente formatee:
fun = "The curent name is {name}".format
names = ["foo", "bar"]
for name in names:
print(fun(name=name))
En versión sin nombres:
fun = "The curent name is {}".format
names = ["foo", "bar"]
for name in names:
print(fun(name))
Una f-string es simplemente una forma más concisa de crear una cadena formateada, reemplazando .format(**names)
con f
. Si no desea que una cadena se evalúe de manera inmediata, no la convierta en una cadena de caracteres. Guárdelo como un literal de cadena normal, y luego llame al format
más tarde cuando desee realizar la interpolación, como lo ha estado haciendo.
Por supuesto, hay una alternativa con eval
.
template.txt
:
f''El nombre actual es {nombre} ''
Código:
>>> template_a = open(''template.txt'').read()
>>> names = ''foo'', ''bar''
>>> for name in names:
... print(eval(template_a))
...
The current name is foo
The current name is bar
Pero luego, todo lo que ha logrado hacer es reemplazar str.format
con eval
, que seguramente no vale la pena. Simplemente siga utilizando cadenas regulares con una llamada de format
.
Una sugerencia que utiliza f-cuerdas. Haga su evaluación en el nivel lógico donde se producen las plantillas y pásela como un generador. Puedes desenrollarlo en el punto que elijas, usando f-strings
In [46]: names = (i for i in (''The CIO, Reed'', ''The homeless guy, Arnot'', ''The security guard Spencer''))
In [47]: po = (f''Strangely, {next(names)} has a nice {i}'' for i in (" nice house", " fast car", " big boat"))
In [48]: while True:
...: try:
...: print(next(po))
...: except StopIteration:
...: break
...:
Strangely, The CIO, Reed has a nice nice house
Strangely, The homeless guy, Arnot has a nice fast car
Strangely, The security guard Spencer has a nice big boat
Usar .format no es una respuesta correcta a esta pregunta. Las cadenas F de Python son muy diferentes de las plantillas str.format () ... pueden contener código u otras operaciones costosas, de ahí la necesidad de aplazamiento.
Aquí hay un ejemplo de un registrador diferido. Esto utiliza el preámbulo normal de logging.getLogger, pero luego agrega nuevas funciones que interpretan la cadena de caracteres solo si el nivel de registro es correcto.
log = logging.getLogger(__name__)
def __deferred_flog(log, fstr, level, *args):
if log.isEnabledFor(level):
import inspect
frame = inspect.currentframe().f_back.f_back
try:
fstr = ''f"'' + fstr + ''"''
log.log(level, eval(fstr, frame.f_globals, frame.f_locals))
finally:
del frame
log.fdebug = lambda fstr, *args: __deferred_flog(log, fstr, logging.DEBUG, *args)
log.finfo = lambda fstr, *args: __deferred_flog(log, fstr, logging.INFO, *args)
Esto tiene la ventaja de poder hacer cosas como: log.fdebug("{obj.dump()")
.... sin volcar el objeto a menos que la depuración esté habilitada.