J'utilise des modèles de chaînes pour générer des fichiers et j'aime la concision des nouvelles chaînes de caractères f à cette fin, afin de réduire mon code de modèle précédent à partir de quelque chose comme ceci:
template_a = "The current name is {name}"
names = ["foo", "bar"]
for name in names:
print (template_a.format(**locals()))
Maintenant, je peux le faire, en remplaçant directement les variables:
names = ["foo", "bar"]
for name in names:
print (f"The current name is {name}")
Cependant, il est parfois judicieux de définir le modèle ailleurs - plus haut dans le code, ou importé depuis un fichier ou quelque chose d'autre. Cela signifie que le modèle est une chaîne statique contenant des balises de formatage . Quelque chose devrait arriver à la chaîne pour dire à l'interprète d'interpréter la chaîne comme une nouvelle chaîne f, mais je ne sais pas s'il existe une telle chose.
Est-il possible d'introduire une chaîne et qu'il soit interprété comme une chaîne f pour éviter d'utiliser l'appel .format(**locals())
?
Idéalement, je veux pouvoir coder comme ceci ... (où magic_fstring_function
est l'endroit où la partie que je ne comprends pas entre en jeu):
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)
... avec cette sortie désirée (sans lire le fichier deux fois):
The current name is foo
The current name is bar
... mais le résultat obtenu est:
The current name is {name}
The current name is {name}
Voici un "Idéal 2" complet.
Ce n'est pas une chaîne de caractères, elle n'utilise même pas les chaînes de caractères. Mais c'est comme demandé. Syntaxe exactement comme spécifiée. Aucun problème de sécurité, car nous n'utilisons pas eval.
Il utilise une petite classe et implémente __str__
qui est automatiquement appelé par print. Pour échapper à l'étendue limitée de la classe, nous utilisons le module inspect
pour sauter d'une image à la fois et voir les variables auxquelles l'appelant a accès.
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
Une chaîne de caractères est simplement un moyen plus concis de créer une chaîne formatée, en remplaçant .format(**names)
par f
. Si vous ne voulez pas qu'une chaîne soit immédiatement évaluée de cette manière, n'en faites pas une chaîne de caractères. Enregistrez-le en tant que littéral de chaîne ordinaire, puis appelez format
ultérieurement lorsque vous souhaitez effectuer l'interpolation, comme vous l'avez fait auparavant.
Bien sûr, il existe une alternative avec eval
.
template.txt
:
f'Le nom actuel est {nom} '
Code:
>>> 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
Mais tout ce que vous avez réussi à faire est de remplacer str.format
par eval
, ce qui n’en vaut sûrement pas la peine. Continuez simplement à utiliser des chaînes normales avec un appel format
.
Cela signifie que le modèle est une chaîne statique contenant des balises de formatage.
Oui, c’est exactement pourquoi nous avons des littéraux avec des champs de remplacement et .format
. Nous pouvons donc remplacer les champs à tout moment en appelant format
.
Quelque chose devrait arriver à la chaîne pour dire à l'interprète d'interpréter la chaîne comme une nouvelle chaîne f
C'est le préfixe f/F
. Vous pouvez l'envelopper dans une fonction et reporter l'évaluation au moment de l'appel mais bien sûr, cela entraîne des frais supplémentaires:
template_a = lambda: f"The current name is {name}"
names = ["foo", "bar"]
for name in names:
print (template_a())
Qui imprime:
The current name is foo
The current name is bar
mais se sent mal et est limité par le fait que vous ne pouvez que jeter un coup d’œil sur l’espace de noms global dans vos remplacements. Essayer de l'utiliser dans une situation qui nécessite des noms locaux échouera misérablement à moins d'être passé à la chaîne en tant qu'arguments (ce qui bat totalement le point).
Existe-t-il un moyen d'introduire une chaîne et de la faire interpréter comme une chaîne f pour éviter d'utiliser l'appel
.format(**locals())
?
Autre qu’une fonction (limitations incluses), non, alors tenez-vous-en à .format
.
Ou peut-être n'utilisez pas de chaînes de caractères, mais simplement le format:
fun = "The curent name is {name}".format
names = ["foo", "bar"]
for name in names:
print(fun(name=name))
En version sans nom:
fun = "The curent name is {}".format
names = ["foo", "bar"]
for name in names:
print(fun(name))
Une façon concise d’évaluer une chaîne en tant que chaîne f (avec toutes ses fonctionnalités) utilise la fonction suivante:
def fstr(template):
return eval(f"f'{template}'")
Ensuite, vous pouvez faire:
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
Et, contrairement à beaucoup d'autres solutions proposées, vous pouvez également faire:
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
Utiliser .format n’est pas une réponse correcte à cette question. Les chaînes de caractères python sont très différentes des modèles str.format () ... elles peuvent contenir du code ou d’autres opérations coûteuses, d’où la nécessité d’un report.
Voici un exemple d'enregistreur différé. Ceci utilise le préambule normal de logging.getLogger, mais ajoute ensuite de nouvelles fonctions qui interprètent la chaîne f seulement si le niveau de journalisation est correct.
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)
Cela présente l’avantage de pouvoir effectuer des opérations telles que: log.fdebug("{obj.dump()")
.... sans vider l’objet, sauf si le débogage est activé.
Une suggestion qui utilise des f-strings. Faites votre évaluation au niveau logique Où le modèle se produit et transmettez-le en tant que générateur . Vous pouvez le dérouler à tout moment de votre choix, en utilisant des chaînes de caractères f.
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