Как отложить/отложить оценку f-строк?
Я использую строки шаблонов для создания некоторых файлов, и мне очень нравится краткость новых f-строк для этой цели, для сокращения моего предыдущего кода шаблона из чего-то вроде этого:
template_a = "The current name is {name}"
names = ["foo", "bar"]
for name in names:
print (template_a.format(**locals()))
Теперь я могу это сделать, напрямую заменяя переменные:
names = ["foo", "bar"]
for name in names:
print (f"The current name is {name}")
Однако иногда имеет смысл иметь шаблон, определенный в другом месте - выше в коде или импортироваться из файла или что-то в этом роде. Это означает, что шаблон является статической строкой с тегами форматирования. Что-то должно произойти со строкой, чтобы заставить интерпретатор интерпретировать строку как новую f-строку, но я не знаю, есть ли такая вещь.
Есть ли способ привести строку и интерпретировать ее как f-строку, чтобы избежать использования .format(**locals())
?
В идеале я хочу, чтобы иметь возможность кодировать вот так... (где magic_fstring_function
- это то место, где я не понимаю):
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)
... с этим желаемым выходом (без чтения файла дважды):
The current name is foo
The current name is bar
... но фактический результат я получаю:
The current name is {name}
The current name is {name}
Ответы
Ответ 1
Здесь полный "Идеал 2".
Это не f-строка, в которой даже не используются f-строки. Но это делается по запросу. Синтаксис точно так же, как указано. Никакой боли в голове безопасности, поскольку мы не используем eval.
Он использует небольшой класс и реализует __str__
, который автоматически вызывается печатью. Чтобы избежать ограниченной области действия класса, мы используем модуль inspect
для перехода по одному кадру вверх и просмотра переменных, к которым имеет доступ вызывающий объект.
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
Ответ 2
f-строка - это просто более сжатый способ создания форматированной строки, заменяя .format(**names)
на f
. Если вы не хотите, чтобы строка была немедленно оценена таким образом, не делайте ее f-строкой. Сохраните его как обычный строковый литерал, а затем вызовите format
на нем позже, когда вы хотите выполнить интерполяцию, как вы делали.
Конечно, есть альтернатива с eval
.
template.txt
:
f 'Текущее имя {name}'
код:
>>> 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
Но тогда все, что вам удалось сделать, это заменить str.format
на eval
, что, безусловно, не стоит. Просто продолжайте использовать обычные строки с вызовом format
.
Ответ 3
Это означает, что шаблон является статической строкой с тегами форматирования в ней
Да, именно поэтому у нас есть литералы с полями замены и .format
, поэтому мы можем заменять поля всякий раз, когда нам нравится, вызывая format
на нем.
Что-то должно произойти со строкой, чтобы интерпретировать интерпретатор как новую f-строку
Это префикс f/F
. Вы можете обернуть его функцией и отложить оценку во время разговора, но, конечно, это навлечет дополнительные накладные расходы:
template_a = lambda: f"The current name is {name}"
names = ["foo", "bar"]
for name in names:
print (template_a())
Что печатает:
The current name is foo
The current name is bar
но чувствует себя не так и ограничивается тем фактом, что вы можете только заглянуть в глобальное пространство имен в своих заменах. Попытка использовать его в ситуации, которая требует, чтобы локальные имена потерпели неудачу, если они не переданы в качестве аргументов (которые полностью превосходят точку).
Есть ли способ вставить строку и интерпретировать ее как f-строку, чтобы избежать использования вызова .format(**locals())
?
Помимо функции (ограничения включены), нет, так что может также придерживаться .format
.
Ответ 4
Краткий способ иметь строку, оцененную как f-строка (с ее полными возможностями), использует следующую функцию:
def fstr(template):
return eval(f"f'{template}'")
Тогда вы можете сделать:
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
И, в отличие от многих других предлагаемых решений, вы также можете:
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
Ответ 5
Или, может быть, не использовать f-строки, просто формат:
fun = "The curent name is {name}".format
names = ["foo", "bar"]
for name in names:
print(fun(name=name))
В версии без имен:
fun = "The curent name is {}".format
names = ["foo", "bar"]
for name in names:
print(fun(name))
Ответ 6
Использование .format не является правильным ответом на этот вопрос. F-строки Python сильно отличаются от шаблонов str.format()... они могут содержать код или другие дорогостоящие операции - отсюда и необходимость отсрочки.
Вот пример отложенного регистратора. При этом используется обычная преамбула logging.getLogger, но затем добавляются новые функции, которые интерпретируют f-строку, только если уровень журнала правильный.
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)
Это имеет то преимущество, что может выполнять такие действия, как: log.fdebug("{obj.dump()}")
.... без выгрузки объекта, если не включена отладка.
ИМХО: Это должна была быть операция по умолчанию для f-строк, однако сейчас уже слишком поздно. Оценка F-строки может иметь массивные и непреднамеренные побочные эффекты, и отсроченное выполнение этого действия изменит выполнение программы.
Для того чтобы сделать f-строки должным образом отложенными, python потребуется какой-то способ явного переключения поведения. Может быть, использовать букву "г"? ;)
Ответ 7
То, что вы хотите, похоже, рассматривается как расширение enhancement.
.Между тем - из связанного обсуждения - кажется, что это был бы разумный обходной путь, который не требует использования 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)
Выход:
The current name, number is 'foo', 41
The current name, number is 'bar', 42
Ответ 8
Вдохновленный ответом kadee, следующее может быть использовано для определения класса deferred-f-string.
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)
что именно то, что вопрос задан
Ответ 9
Предложение, которое использует f-строки. Сделайте свою оценку на логическом уровне, где происходит шаблон, и передайте его как генератор. Вы можете развернуть его в любой момент, который вы выберете, используя 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