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 de un archivo o algo. Esto significa que la plantilla es una cadena estática con etiquetas de formato. Algo tendría que sucederle 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 introducir una cadena e interpretarla como una cadena f para evitar usar la .format(**locals())
llamada?
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 la salida real que obtengo es:
The current name is {name}
The current name is {name}
f
cuerda. Unaf
cadena no es datos y ciertamente no es una cadena; es código. (Compruébelo con eldis
módulo). Si desea que el código se evalúe más adelante, utilice una función..format(**locals())
, aunque cosméticamente más agradable. Hasta que se implemente PEP-501.str.format()
método menos característico y extremadamente lento que admite la evaluación diferida por un lado y una sintaxis de cadena f más funcional y extremadamente rápida que no admite la evaluación diferida por el otro. Así que todavía necesitamos ambos y Python todavía no tiene un formateador de cadenas estándar. Inserte el meme de estándares xkcd.Respuestas:
Aquí hay un "Ideal 2" completo.
No es una f-string, ni siquiera usa f-strings, pero hace lo solicitado. Sintaxis exactamente como se especifica. Sin dolores de cabeza de seguridad ya que no estamos usando
eval()
.Utiliza una pequeña clase e implementos
__str__
que se llama automáticamente por impresión. Para escapar del alcance limitado de la clase, usamos elinspect
módulo para saltar un fotograma 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
fuente
template = "The beginning of the name is {name[:4]}"
(->TypeError: string indices must be integers
)str.format
. Solía pensar que las cuerdas f son solo azúcar sintáctico para algo así,str.format(**locals(), **globals())
pero obviamente estaba equivocado.inspect
es una bandera roja.__slots__
aquí para el uso reducido de memoria?Sí, esa es exactamente la razón por la que tenemos literales con campos de reemplazo y
.format
, por lo tanto, podemos reemplazar los campos cuando queramos invocándoloformat
.Ese es el prefijo
f/F
. Puede envolverlo en una función y posponer la evaluación durante el tiempo de 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 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. 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).
Aparte de una función (limitaciones incluidas), no, por lo que también podría seguir
.format
.fuente
Una 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 bar
Y, 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 BARBAR
fuente
name
es global. Las f-strings deben aplazarse en la evaluación, pero la clase FString necesita crear una lista de referencias a los argumentos de ámbito mirando a los llamadores locales y globales ... y luego evaluar la cadena cuando se usa.eval()
generalmente se desaconseja el uso de .Una cadena f es simplemente una forma más concisa de crear una cadena formateada, reemplazando
.format(**names)
porf
. Si no desea que una cadena se evalúe inmediatamente de esa manera, no la convierta en una cadena f. Guárdelo como un literal de cadena ordinario y luego invocaloformat
más tarde cuando desee realizar la interpolación, como lo ha estado haciendo.Por supuesto, existe una alternativa con
eval
.template.txt
: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 todo lo que ha logrado hacer es reemplazar
str.format
coneval
, lo que seguramente no vale la pena. Simplemente siga usando cadenas regulares con unaformat
llamada.fuente
The current name is {name}
dentro deltemplate.txt
archivo y luego usarprint(template_a.format(name=name))
(o.format(**locals())
). El código tiene aproximadamente 10 caracteres más, pero no presenta ningún problema de seguridad debido aeval
.eval
nos permite escribirf'{name}'
y retrasar la evaluación dename
hasta que lo deseemos, es inferior a simplemente crear una cadena de plantilla regular y luego llamarlaformat
, como ya estaba haciendo el OP..format
no es equivalente a un F-cadena, que puede soportar usted comenta:DNA = "TATTCGCGGAAAATATTTTGA"; fragment = f"{DNA[2:8]}"; failed_fragment = "{DNA[2:8]}".format(**locals())
. El intento de crearfailed_fragment
resultados enTypeError: string indices must be integers
.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.
A continuación, se muestra un ejemplo de registrador diferido. Esto usa el preámbulo normal de logging.getLogger, pero luego agrega nuevas funciones que interpretan la cadena f 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.En mi humilde opinión: esta debería haber sido la operación predeterminada de f-strings, sin embargo, ahora es demasiado tarde . La evaluación de F-string puede tener efectos secundarios masivos y no deseados, y si eso sucede de manera diferida cambiará la ejecución del programa.
Para hacer que las f-strings se difieran correctamente, Python necesitaría alguna forma de cambiar explícitamente el comportamiento. ¿Quizás usar la letra 'g'? ;)
Se ha señalado que el registro diferido no debería fallar si hay un error en el convertidor de cadenas. La solución anterior también puede hacer esto, cambiar
finally:
aexcept:
y pegarlog.exception
allí.fuente
%timeit log.finfo(f"{bar=}") 91.9 µs ± 7.45 µs per loop %timeit log.info(f"{bar=}") 56.2 µs ± 630 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each) log.setLevel(logging.CRITICAL) %timeit log.finfo("{bar=}") 575 ns ± 2.9 ns per loop %timeit log.info(f"{bar=}") 480 ns ± 9.37 ns per loop %timeit log.finfo("") 571 ns ± 2.66 ns per loop %timeit log.info(f"") 380 ns ± 0.92 ns per loop %timeit log.info("") 367 ns ± 1.65 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
Lo que desea parece estar considerado como una mejora de Python .
Mientras tanto, a partir de la discusión vinculada, lo siguiente parece ser una solución alternativa razonable que no requiere el uso
eval()
:class FL: def __init__(self, func): self.func = func def __str__(self): return self.func() template_a = FL(lambda: f"The current name, number is {name!r}, {number+1}") names = "foo", "bar" numbers = 40, 41 for name, number in zip(names, numbers): print(template_a)
Salida:
fuente
inspirado en la respuesta de kadee , lo siguiente se puede usar para definir una clase de cadena f diferida.
class FStr: def __init__(self, s): self._s = s def __repr__(self): return eval(f"f'{self._s}'") ... template_a = FStr('The current name is {name}') names = ["foo", "bar"] for name in names: print (template_a)
que es exactamente lo que pedía la pregunta
fuente
O tal vez no use f-strings, solo 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))
fuente
fun = "{DNA[2:8]}".format; DNA = "TATTCGCGGAAAATATTTTGA"; fun(DNA=DNA)
. ->TypeError: string indices must be integers
Qué tal si:
s = 'Hi, {foo}!' s > 'Hi, {foo}!' s.format(foo='Bar') > 'Hi, Bar!'
fuente
Una sugerencia que usa f-strings. Haga su evaluación en el nivel lógico donde se está produciendo la plantilla y páselo como un generador. Puede desenrollarlo en cualquier punto que elija, utilizando 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
fuente