Używam ciągów szablonów do generowania niektórych plików i uwielbiam zwięzłość nowych ciągów f do tego celu, aby zredukować mój poprzedni kod szablonu z czegoś takiego:
template_a = "The current name is {name}"
names = ["foo", "bar"]
for name in names:
print (template_a.format(**locals()))
Teraz mogę to zrobić, bezpośrednio zastępując zmienne:
names = ["foo", "bar"]
for name in names:
print (f"The current name is {name}")
Czasami jednak sensowne jest zdefiniowanie szablonu w innym miejscu - wyżej w kodzie lub zaimportowanie z pliku lub czegoś podobnego. Oznacza to, że szablon jest statycznym ciągiem zawierającym znaczniki formatujące. Coś musiałoby się stać ze stringiem, aby powiedzieć interpreterowi, aby zinterpretował go jako nową f-string, ale nie wiem, czy coś takiego istnieje.
Czy istnieje sposób, aby wprowadzić ciąg i interpretować go jako ciąg f, aby uniknąć używania .format(**locals())
wywołania?
Idealnie chciałbym móc kodować w ten sposób ... (gdzie magic_fstring_function
jest część, której nie rozumiem):
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)
... z tym żądanym wyjściem (bez dwukrotnego odczytu pliku):
The current name is foo
The current name is bar
... ale rzeczywisty wynik, jaki otrzymuję, to:
The current name is {name}
The current name is {name}
f
sznurka.f
Ciąg nie jest dane, a to z pewnością nie jest to ciąg znaków; to jest kod. (Sprawdź to zdis
modułem). Jeśli chcesz, aby kod był oceniany w późniejszym czasie, użyj funkcji..format(**locals())
, chociaż kosmetycznie ładniejszy. Do czasu wdrożenia PEP-501.str.format()
metodą obsługującą odroczoną ocenę z jednej strony, a bardziej funkcjonalną, niezwykle szybką składnią f-string, która nie obsługuje odroczonej oceny z drugiej. Więc nadal potrzebujemy obu, a Python nadal nie ma standardowego programu formatującego ciągi. Wstaw meme standardów xkcd.Odpowiedzi:
Oto kompletny „Idealny 2”.
To nie jest ciąg f - nie używa nawet ciągów f - ale działa zgodnie z żądaniem. Składnia dokładnie taka, jak określono. Żadnych problemów związanych z bezpieczeństwem, ponieważ nie używamy
eval()
.Używa małej klasy i narzędzi,
__str__
które są automatycznie wywoływane przez print. Aby uciec od ograniczonego zakresu klasy, używamyinspect
modułu, aby przeskoczyć jedną ramkę w górę i zobaczyć zmienne, do których ma dostęp wywołujący.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
źródło
template = "The beginning of the name is {name[:4]}"
(->TypeError: string indices must be integers
)str.format
. Kiedyś myślałem, że f-stringi są po prostu cukrem syntaktycznym na coś takiego,str.format(**locals(), **globals())
ale oczywiście się myliłem.inspect
to czerwona flaga.__slots__
tutaj do zmniejszonego zużycia pamięci?Tak, właśnie dlatego mamy literały z polami zastępczymi
.format
, dzięki czemu możemy zastępować pola w dowolnym momencie, wywołującformat
je.To jest przedrostek
f/F
. Możesz owinąć to w funkcję i odłożyć ocenę w czasie połączenia, ale oczywiście wiąże się to z dodatkowym narzutem:template_a = lambda: f"The current name is {name}" names = ["foo", "bar"] for name in names: print (template_a())
Który drukuje:
The current name is foo The current name is bar
ale czuje się źle i jest ograniczony przez fakt, że możesz tylko zerknąć na globalną przestrzeń nazw w swoich zamiennikach. Próba użycia go w sytuacji, która wymaga nazw lokalnych, zakończy się niepowodzeniem, chyba że zostanie przekazana do łańcucha jako argumenty (co całkowicie mija się z celem).
Inna niż funkcja (w tym ograniczenia), nie, więc równie dobrze może się trzymać
.format
.źródło
Zwięzłym sposobem na ocenę łańcucha jako f-string (z jego pełnymi możliwościami) jest użycie następującej funkcji:
def fstr(template): return eval(f"f'{template}'")
Następnie możesz:
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
W przeciwieństwie do wielu innych proponowanych rozwiązań możesz również:
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
źródło
name
jest globalny. Ciągi f powinny zostać odroczone w ocenie, ale klasa FString musi utworzyć listę odwołań do argumentów w określonym zakresie, patrząc na lokalne i globalne wywołań, a następnie oceniać łańcuch, gdy jest używany.eval()
generalnie odradza się używanie .F-string to po prostu bardziej zwięzły sposób tworzenia sformatowanego ciągu, zastępując
.format(**names)
gof
. Jeśli nie chcesz, aby ciąg był natychmiast oceniany w taki sposób, nie rób z niego f-string. Zapisz go jako zwykły literał łańcuchowy, a następnie przywołajformat
go później, gdy chcesz wykonać interpolację, tak jak to robiłeś.Oczywiście istnieje alternatywa z
eval
.template.txt
:Kod:
>>> 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
Ale to wszystko, co udało się zrobić, to wymienić
str.format
sięeval
, co nie jest z pewnością warto. Po prostu używaj zwykłych ciągów znaków podczasformat
wywołania.źródło
The current name is {name}
wtemplate.txt
pliku, a następnie użyćprint(template_a.format(name=name))
(lub.format(**locals())
). Kod jest dłuższy o około 10 znaków, ale nie wprowadza żadnych możliwych problemów związanych z bezpieczeństwemeval
.eval
pozwala nam napisaćf'{name}'
i opóźnić ocenęname
do pożądanego, jest gorszy od zwykłego utworzenia zwykłego ciągu szablonu, a następnie wywołaniaformat
go, tak jak robił to OP..format
nie jest równoznaczne z f-string, które mogą Ci pomóc skomentować:DNA = "TATTCGCGGAAAATATTTTGA"; fragment = f"{DNA[2:8]}"; failed_fragment = "{DNA[2:8]}".format(**locals())
. Próba stworzeniafailed_fragment
wyników wTypeError: string indices must be integers
.Używanie .format nie jest poprawną odpowiedzią na to pytanie. Pythonowe f-stringi bardzo różnią się od szablonów str.format () ... mogą zawierać kod lub inne kosztowne operacje - stąd potrzeba odroczenia.
Oto przykład odroczonego rejestratora. Używa normalnej preambuły logowania .getLogger, ale dodaje nowe funkcje, które interpretują f-string tylko wtedy, gdy poziom dziennika jest poprawny.
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)
Ma to tę zaletę, że można robić takie rzeczy jak:
log.fdebug("{obj.dump()}")
.... bez zrzucania obiektu, chyba że debugowanie jest włączone.IMHO: To powinno być domyślne działanie f-stringów, ale teraz jest już za późno . Ocena łańcucha F może mieć ogromne i niezamierzone skutki uboczne, a wystąpienie tego w sposób odroczony zmieni wykonywanie programu.
Aby poprawnie odroczyć ciągi f, Python potrzebowałby jakiegoś sposobu jawnego przełączania zachowania. Może użyj litery „g”? ;)
Wskazano, że odroczone rejestrowanie nie powinno ulegać awarii, jeśli w konwerterze ciągów wystąpi błąd. Powyższe rozwiązanie można to zrobić również, zmienić
finally:
sięexcept:
i trzymaćlog.exception
tam.źródło
%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)
Wydaje się, że to, czego chcesz, jest uważane za ulepszenie Pythona .
Tymczasem - z połączonej dyskusji - wydaje się, że byłoby to rozsądne obejście, które nie wymaga użycia
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)
Wynik:
źródło
zainspirowany odpowiedzią Kadee , do zdefiniowania klasy odroczonego f-string można użyć następującego.
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)
co jest dokładnie tym, o co chodziło w pytaniu
źródło
A może nie używaj f-stringów, po prostu formatuj:
fun = "The curent name is {name}".format names = ["foo", "bar"] for name in names: print(fun(name=name))
W wersji bez nazw:
fun = "The curent name is {}".format names = ["foo", "bar"] for name in names: print(fun(name))
źródło
fun = "{DNA[2:8]}".format; DNA = "TATTCGCGGAAAATATTTTGA"; fun(DNA=DNA)
. ->TypeError: string indices must be integers
Co powiesz na:
s = 'Hi, {foo}!' s > 'Hi, {foo}!' s.format(foo='Bar') > 'Hi, Bar!'
źródło
Sugestia wykorzystująca f-stringi. Dokonaj oceny na poziomie logicznym, na którym ma miejsce tworzenie szablonów i przekaż ją jako generator. Możesz go rozwinąć w dowolnym momencie, używając f-stringów
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
źródło