wake-up-neo.net

Kann ich die Auswertung von F-Strings verschieben/verschieben?

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}
35
JDAnders

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
10
Paul Panzer

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.

12
TigerhawkT3

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))
3
msztolcman

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

2
user3204459

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
1
martineau

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
1
kadee

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. 

0
Erik Aronesty

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  
0
Ron Lawhorn