Я использую строки шаблона для генерации некоторых файлов, и мне нравится лаконичность новых 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}
f
строкой.f
Строка не данные, и это, безусловно , не является строка; это код. (Проверьте это с помощьюdis
модуля.) Если вы хотите, чтобы код был оценен позже, вы используете функцию..format(**locals())
, хотя косметически лучше. Пока не будет реализован PEP-501.str.format()
методом, поддерживающим отложенное вычисление, с одной стороны, и более функциональным, чрезвычайно быстрым синтаксисом f-строки, не поддерживающим отложенное вычисление, с другой. Так что нам все еще нужны оба, а в Python по-прежнему нет стандартного средства форматирования строк. Вставьте мем стандартов xkcd.Ответы:
Вот и полный «Идеал 2».
Это не f-строка - она даже не использует f-строки - но выполняет то, что было запрошено. Синтаксис точно такой, как указано. Никаких проблем с безопасностью, поскольку мы не используем
eval()
.Он использует небольшой класс и реализует,
__str__
который автоматически вызывается print. Чтобы избежать ограниченной области действия класса, мы используем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
источник
template = "The beginning of the name is {name[:4]}"
(->TypeError: string indices must be integers
)str.format
. Раньше я думал, что f-строки - это просто синтаксический сахар для чего-то вроде,str.format(**locals(), **globals())
но, очевидно, я был неправ.inspect
это красный флаг.__slots__
здесь для уменьшения использования памяти?Да, именно поэтому у нас есть литералы с заменяющими полями, и
.format
поэтому мы можем заменить поля, когда захотим, вызвавformat
его.Это приставка
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
но кажется неправильным и ограничивается тем фактом, что вы можете только взглянуть на глобальное пространство имен в ваших заменах. Попытка использовать его в ситуации, требующей локальных имен, потерпит неудачу, если не будет передана в строку в качестве аргументов (что полностью превосходит суть).
Помимо функции (включая ограничения), нет, так что можно придерживаться
.format
.источник
Краткий способ оценки строки как 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
источник
name
это глобальность. f-строки должны быть отложены при оценке, но классу FString необходимо создать список ссылок на аргументы с заданной областью действия, просмотрев локальные и глобальные переменные вызывающих объектов ... и затем оценить строку при использовании.eval()
обычно не рекомендуется.F-строка - это просто более краткий способ создания форматированной строки, заменяющей
.format(**names)
наf
. Если вы не хотите, чтобы строка немедленно оценивалась таким образом, не делайте ее f-строкой. Сохраните его как обычный строковый литерал, а затем вызовитеformat
его позже, когда вы захотите выполнить интерполяцию, как вы это делали раньше.Конечно, есть альтернатива
eval
.template.txt
:Код:
>>> 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
вызове.источник
The current name is {name}
внутриtemplate.txt
файла, а затем использоватьprint(template_a.format(name=name))
(или.format(**locals())
). Код примерно на 10 символов длиннее, но он не представляет никаких возможных проблем с безопасностью из-заeval
.eval
он позволяет нам писатьf'{name}'
и откладывать оценкуname
до желаемого, он уступает простому созданию обычной строки шаблона и последующему ее вызовуformat
, как это уже делал OP..format
не эквивалентен F-строка, которая может поддерживать вас комментарии:DNA = "TATTCGCGGAAAATATTTTGA"; fragment = f"{DNA[2:8]}"; failed_fragment = "{DNA[2:8]}".format(**locals())
. Попытка создатьfailed_fragment
результаты вTypeError: string indices must be integers
.Использование .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 потребуется какой-то способ явного переключения поведения. Может быть, использовать букву «г»? ;)
Было указано, что отложенное ведение журнала не должно приводить к сбою, если есть ошибка в преобразователе строк. Выше решение может сделать это , а также, изменить
finally:
кexcept:
, и воткнутьlog.exception
туда.источник
%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)
То, что вы хотите, похоже, рассматривается как усовершенствование Python .
Между тем - из связанного обсуждения - следующее кажется разумным обходным путем, который не требует использования
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)
Выход:
источник
вдохновленный ответом kadee , следующее можно использовать для определения класса deferred-f-string.
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)
это именно то, о чем спрашивал
источник
Или, может быть, не используйте 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))
источник
fun = "{DNA[2:8]}".format; DNA = "TATTCGCGGAAAATATTTTGA"; fun(DNA=DNA)
. ->TypeError: string indices must be integers
Как насчет:
s = 'Hi, {foo}!' s > 'Hi, {foo}!' s.format(foo='Bar') > 'Hi, Bar!'
источник
Предложение, использующее 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
источник