Ich verwende Template-Strings, um einige Dateien zu generieren, und ich liebe die Prägnanz der neuen F-Strings zu diesem Zweck, um meinen vorherigen Template-Code von etwas zu reduzieren:
template_a = "The current name is {name}"
names = ["foo", "bar"]
for name in names:
print (template_a.format(**locals()))
Jetzt kann ich dies direkt tun und Variablen ersetzen:
names = ["foo", "bar"]
for name in names:
print (f"The current name is {name}")
Manchmal ist es jedoch sinnvoll, die Vorlage an einer anderen Stelle zu definieren - höher im Code oder aus einer Datei oder etwas importiert. Dies bedeutet, dass die Vorlage eine statische Zeichenfolge mit Formatierungs-Tags ist . Der Zeichenfolge muss etwas passieren, damit der Interpreter die Zeichenfolge als neue F-Zeichenfolge interpretiert, aber ich weiß nicht, ob es so etwas gibt.
Gibt es eine Möglichkeit, einen String einzufügen und ihn als f-String interpretieren zu lassen, um den Aufruf von .format(**locals())
zu vermeiden?
Idealerweise möchte ich in der Lage sein, so zu codieren ... (wobei magic_fstring_function
der Teil ist, den ich nicht verstehe):
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)
... mit dieser gewünschten Ausgabe (ohne zweimaliges Lesen der Datei):
The current name is foo
The current name is bar
... aber die eigentliche Ausgabe ist:
The current name is {name}
The current name is {name}
Hier ist ein komplettes "Ideal 2".
Es ist kein F-String, es werden nicht einmal F-Strings verwendet. Aber es tut wie gewünscht. Syntax genau wie angegeben. Kein Sicherheitskopf schmerzt, da wir eval nicht verwenden.
Es verwendet eine kleine Klasse und implementiert __str__
, das automatisch von print aufgerufen wird. Um dem begrenzten Bereich der Klasse zu entgehen, verwenden wir das Modul inspect
, um einen Frame zu springen und die Variablen zu sehen, auf die der Aufrufer Zugriff hat.
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
Ein F-String ist eine einfachere Methode, um einen formatierten String zu erstellen, der .format(**names)
durch f
ersetzt. Wenn Sie nicht möchten, dass ein String auf diese Weise sofort ausgewertet wird, sollten Sie ihn nicht zu einem F-String machen. Speichern Sie es als normales String-Literal und rufen Sie später format
auf, wenn Sie die Interpolation durchführen möchten, wie Sie es getan haben.
Natürlich gibt es eine Alternative mit eval
.
template.txt
:
der aktuelle Name ist {Name} '
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
Alles, was Sie bisher geschafft haben, ist, str.format
durch eval
zu ersetzen, was sich sicherlich nicht lohnt. Verwenden Sie einfach normale Zeichenfolgen mit einem Aufruf von format
.
Dies bedeutet, dass die Vorlage eine statische Zeichenfolge mit Formatierungs-Tags ist
Ja, genau aus diesem Grund haben wir Literale mit Ersetzungsfeldern und .format
, sodass wir die Felder jederzeit ersetzen können, indem wir format
aufrufen.
Der Zeichenfolge muss etwas passieren, damit der Interpreter die Zeichenfolge als neue F-Zeichenfolge interpretiert
Das ist das Präfix f/F
. Sie könnten es in eine Funktion einschließen und die Auswertung während der Gesprächszeit verschieben, was natürlich zusätzlichen Aufwand verursacht:
template_a = lambda: f"The current name is {name}"
names = ["foo", "bar"]
for name in names:
print (template_a())
Welche druckt aus:
The current name is foo
The current name is bar
fühlt sich aber falsch an und ist durch die Tatsache eingeschränkt, dass Sie nur den globalen Namespace in Ihren Ersetzungen betrachten können. Der Versuch, es in einer Situation zu verwenden, in der lokale Namen erforderlich sind, schlägt fehl, wenn es nicht als Argumente an den String übergeben wird (was den Punkt völlig übertrifft).
Gibt es eine Möglichkeit, einen String einzufügen und ihn als f-String interpretieren zu lassen, um den Aufruf von
.format(**locals())
zu vermeiden?
Abgesehen von einer Funktion (Einschränkungen enthalten), nope, könnte also auch .format
bleiben.
Oder verwenden Sie möglicherweise keine F-Strings, sondern formatieren Sie einfach:
fun = "The curent name is {name}".format
names = ["foo", "bar"]
for name in names:
print(fun(name=name))
In Version ohne Namen:
fun = "The curent name is {}".format
names = ["foo", "bar"]
for name in names:
print(fun(name))
inspiriert von Antwort von kadee kann Folgendes verwendet werden, um eine Klasse für verzögerte f-Strings zu definieren.
class FStr:
def __init__(self, s):
self._s = s
def __str__(self):
return eval(f"f'{self._s}'")
def __repr__(self):
return self.__str__()
...
template_a = FStr('The current name is {name}')
names = ["foo", "bar"]
for name in names:
print (template_a)
das ist genau das, was die Frage gestellt hat
Was Sie wollen, wird anscheinend als Python Enhancement betrachtet.
Aus der verknüpften Diskussion geht hervor, dass das Folgende eine vernünftige Problemumgehung darstellt, für die die Verwendung von eval()
nicht erforderlich ist:
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)
Ausgabe:
The current name, number is 'foo', 41
The current name, number is 'bar', 42
Eine prägnante Möglichkeit, eine Zeichenfolge als F-Zeichenfolge (mit ihren vollen Fähigkeiten) auszuwerten, besteht in der folgenden Funktion:
def fstr(template):
return eval(f"f'{template}'")
Dann können Sie tun:
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
Im Gegensatz zu vielen anderen Lösungsvorschlägen können Sie auch Folgendes tun:
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
Die Verwendung von .format ist keine korrekte Antwort auf diese Frage. Python-F-Zeichenfolgen unterscheiden sich stark von str.format () -Vorlagen ... Sie können Code oder andere kostspielige Operationen enthalten - daher ist eine Zurückstellung erforderlich.
Hier ist ein Beispiel eines verzögerten Loggers. Dies verwendet die normale Präambel von logging.getLogger, fügt jedoch neue Funktionen hinzu, die den F-String nur dann interpretieren, wenn der Log-Level korrekt ist.
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)
Dies hat den Vorteil, dass Sie Folgendes tun können: log.fdebug("{obj.dump()")
.... ohne Ablegen des Objekts, wenn das Debugging nicht aktiviert ist.
Ein Vorschlag, der f-Strings verwendet. Führen Sie Ihre Bewertung auf der logischen Ebene aus, auf der sich das Templating befindet, und übergeben Sie es als Generator. Sie können es an jedem beliebigen Punkt abwickeln, indem Sie f-Zeichenfolgen verwenden
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