Как отложить/отложить оценку 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}

person JDAnders    schedule 27.02.2017    source источник
comment
Вы не можете сделать это со строкой f. Строка f не является данными и уж точно не строкой; это код. (Проверьте это с помощью модуля dis.) Если вы хотите, чтобы код оценивался позже, вы используете функцию.   -  person kindall    schedule 28.02.2017
comment
К вашему сведению, PEP 501 предложил функцию, близкую к вашему первому идеалу, но она в настоящее время отложено в ожидании дальнейшего опыта работы с [f-strings].   -  person jwodder    schedule 28.02.2017
comment
Шаблон - это статическая строка, но f-строка - это не строка, а объект кода, как сказал @kindall. Я думаю, что f-строка привязывается к переменным сразу же, когда она создается (в Python 3.6,7), а не когда она в конечном итоге используется. Так что f-string может быть менее полезным, чем ваш старый уродливый .format(**locals()), хотя и более привлекательный с косметической точки зрения. Пока не будет реализован PEP-501.   -  person smci    schedule 15.10.2018
comment
Гвидо спас нас, но PEP 498 действительно все испортил. Отложенная оценка, описанная в PEP 501, абсолютно должна была быть встроена в ядро. реализация f-строки. Теперь нам остается торговаться между менее функциональным, чрезвычайно медленным str.format() методом, поддерживающим отложенное вычисление, с одной стороны, и более функциональным, чрезвычайно быстрым синтаксисом f-строки, не поддерживающим отложенное вычисление, с другой. Так что нам по-прежнему нужны оба, а в Python по-прежнему нет стандартного средства форматирования строк. Вставить мем стандартов xkcd.   -  person Cecil Curry    schedule 16.09.2020


Ответы (11)


Вот и полный "Идеал 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
person Paul Panzer    schedule 27.02.2017
comment
Это позволяет определить шаблон в другом месте, но не в виде строки. Один из способов, которым я делаю шаблоны, — это файл, полный {format} маркеров. Можно ли адаптировать это решение к случаю, когда шаблон исходит из open('file').read() (надеюсь, без фактического повторного чтения файла каждый раз)? Я отредактирую вопрос, чтобы уточнить. - person JDAnders; 28.02.2017
comment
@JDAnders Пожалуйста, взгляните на последнюю и самую лучшую версию, которая, я бы сказал, реализует ваш Ideal 2 до запятой. - person Paul Panzer; 28.02.2017
comment
Я собираюсь принять это как ответ, хотя я не думаю, что когда-либо буду использовать его в коде из-за чрезвычайной хитрости. Ну может никогда :). Возможно, разработчики Python смогут использовать его для реализации PEP 501. Если бы мои вопросы заключались в том, как мне справиться с этим сценарием, ответом было бы просто продолжать использовать функцию .format() и ждать разрешения PEP 501. Спасибо, что придумал, как делать то, чего делать не следует, @PaulPanzer - person JDAnders; 28.02.2017
comment
Это не работает, когда шаблон включает в себя что-то более сложное, чем простые имена переменных. Например: template = "The beginning of the name is {name[:4]}" (-> TypeError: string indices must be integers) - person bli; 05.02.2018
comment
@bli Интересно, кажется, это ограничение str.format. Раньше я думал, что f-строки — это просто синтаксический сахар для чего-то вроде str.format(**locals(), **globals()), но, очевидно, я ошибался. - person Paul Panzer; 05.02.2018
comment
Пожалуйста, не используйте это в производстве. inspect — это красный флаг. - person alexandernst; 05.01.2019
comment
У меня есть 2 вопроса, почему проверка красного флага для производства будет ли такой случай исключением или будут ли более жизнеспособные обходные пути? И есть ли что-то против использования __slots__ здесь для уменьшения использования памяти? - person Jab; 16.02.2019
comment
Это гораздо лучший ответ: stackoverflow.com/a/54701045/207661 - person Shital Shah; 08.04.2020

Это означает, что шаблон представляет собой статическую строку с тегами форматирования.

Да, именно поэтому у нас есть литералы с замещающими полями и .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.

person Dimitris Fasarakis Hilliard    schedule 28.02.2017
comment
Забавно, у меня был точно такой же фрагмент. Но я отказался от него из-за ограничений области видимости. (Попробуйте обернуть цикл for функцией.) - person Paul Panzer; 28.02.2017
comment
@PaulPanzer, может быть, вы хотите отредактировать вопрос и снова включить его? Я был бы не против удалить ответ. Это жизнеспособная альтернатива для случая OP. Это не жизнеспособная альтернатива для всех случаев, это скрыто. - person Dimitris Fasarakis Hilliard; 28.02.2017
comment
Нет, все в порядке, держи. Я намного счастливее с моим новым решением. Но я понимаю вашу точку зрения, что это жизнеспособно, если вы знаете о его ограничениях. Может быть, вы могли бы добавить небольшое предупреждение в свой пост, чтобы никто не прострелил себе ногу, используя ее неправильно? - person Paul Panzer; 28.02.2017

Краткий способ оценить строку как 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
person kadee    schedule 07.12.2018
comment
пока лучший ответ! как они не включили эту простую реализацию в качестве встроенной функции, когда представили f-строки? - person user3204459; 15.05.2019
comment
нет, это теряет объем. единственная причина, по которой это работает, заключается в том, что name является глобальным. f-строки должны быть отложены при оценке, но классу FString необходимо создать список ссылок на аргументы с ограниченной областью действия, просматривая локальные и глобальные переменные вызывающей стороны... и затем оценивать строку при ее использовании. - person Erik Aronesty; 14.06.2019
comment
@ user3204459: Потому что возможность выполнять произвольные строки по своей сути представляет угрозу безопасности, поэтому использование eval() обычно не рекомендуется. - person martineau; 12.11.2019
comment
@martineau это должна была быть функция python, чтобы вам не нужно было использовать eval ... плюс, f-string имеет те же риски, что и eval (), поскольку вы можете поместить все что угодно в фигурные скобки, включая вредоносный код, поэтому, если это проблема, то не используйте f-строки - person user3204459; 12.11.2019
comment
@user3204459 user3204459 Это другой риск: eval позволяет запускать произвольный код из строки из любого ненадежного источника (файл, командная строка, веб-сервис, поле формы и т. д.). Код, заключенный в f-строку, может исходить только из самого исходного кода. Конечно, он все еще может причинить вред (например, если вы напишете что-то вроде f'result is { os.system(userSuppliedCommand) }' ), но мне проще писать с ним более безопасный код, чем с eval. - person huelbois; 11.03.2020
comment
Это именно то, что я искал, уклоняясь от «fstr delay». Eval кажется не хуже, чем использование fstrings в целом, так как они, я думаю, обладают одинаковой силой: f{eval('print(42)')} - person user2692263; 09.08.2020
comment
Небольшое расширение этого ответа - вы можете использовать f'f"""{template}"""' для обработки шаблонов с новыми строками в них. В текущей реализации вы получите что-то вроде SyntaxError: EOL while scanning string literal, если в вашем шаблоне есть символ новой строки. - person Max Shenfield; 16.02.2021

Использование .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.

person Erik Aronesty    schedule 17.04.2018
comment
Полностью согласен с этим ответом. Этот вариант использования - это то, о чем я думал, когда искал этот вопрос. - person justhalf; 27.08.2019
comment
Это правильный ответ. Некоторые тайминги: %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) - person Jaleks; 29.07.2020
comment
если есть ошибка в конвертере строк... -- ошибка в том, что он не принимает двойные кавычки в строке. f_string.replace('"', '\\"') работает для экранирования кавычек, но не для уже экранированных кавычек (например, если вы записываете выходные данные). - person Teresa e Junior; 09.02.2021
comment
Не могу отредактировать свой комментарий: вместо этого помогает использование 'f"""' + fstr + '"""'. - person Teresa e Junior; 09.02.2021

F-строка — это просто более краткий способ создания форматированной строки, заменяющий .format(**names) на f. Если вы не хотите, чтобы строка оценивалась таким образом немедленно, не делайте ее f-строкой. Сохраните его как обычный строковый литерал, а затем вызовите для него format позже, когда захотите выполнить интерполяцию, как вы это делали.

Конечно, есть альтернатива с eval.

template.txt:

f'Текущее имя {имя}'

Код:

>>> 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.

person TigerhawkT3    schedule 27.02.2017
comment
Я действительно не вижу преимущества в вашем фрагменте кода. Я имею в виду, что вы всегда можете написать только The current name is {name} внутри файла template.txt, а затем использовать print(template_a.format(name=name)) (или .format(**locals())). Код длиннее примерно на 10 символов, но он не создает никаких возможных проблем с безопасностью из-за eval. - person Bakuriu; 28.02.2017
comment
@Бакуриу - Да; как я уже сказал, хотя eval позволяет нам писать f'{name}' и откладывать оценку name до тех пор, пока это не потребуется, он уступает простому созданию обычной строки шаблона и последующему вызову format, как это уже делал ОП. - person TigerhawkT3; 28.02.2017
comment
F-строка — это просто более краткий способ создания форматированной строки с заменой .format(**names) на f. Не совсем - они используют другой синтаксис. У меня нет достаточно свежего python3 для проверки, но, например, я считаю, что f'{a+b}' работает, а '{a+b}'.format(a=a, b=b) вызывает KeyError . .format(), вероятно, хорош во многих контекстах, но это не замена. - person philh; 26.07.2017
comment
@philh Я думаю, что только что столкнулся с примером, где .format не эквивалентен f-строке, который может поддержать ваш комментарий: DNA = "TATTCGCGGAAAATATTTTGA"; fragment = f"{DNA[2:8]}"; failed_fragment = "{DNA[2:8]}".format(**locals()). Попытка создать failed_fragment приводит к TypeError: string indices must be integers. - person bli; 05.02.2018

То, что вы хотите, похоже, рассматривается как улучшение 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)

Вывод:

The current name, number is 'foo', 41
The current name, number is 'bar', 42
person martineau    schedule 23.08.2019

вдохновленный ответом 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)

именно к этому и был задан вопрос

person user3204459    schedule 15.05.2019

Как насчет:

s = 'Hi, {foo}!'

s
> 'Hi, {foo}!'

s.format(foo='Bar')
> 'Hi, Bar!'
person Denis    schedule 16.07.2020

Или, может быть, не используйте 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))
person msztolcman    schedule 04.03.2017
comment
Это работает не во всех случаях. Пример: fun = "{DNA[2:8]}".format; DNA = "TATTCGCGGAAAATATTTTGA"; fun(DNA=DNA). -› TypeError: string indices must be integers - person bli; 05.02.2018
comment
Но это не работает и при обычном использовании, посмотрите ответ stackoverflow.com/questions/14072810/ - person msztolcman; 06.02.2018

Большинство из этих ответов дадут вам что-то, что иногда ведет себя как f-строки, но в некоторых случаях все они будут неправильными. На pypi есть пакет f-yeah, который делает все это, но стоит вам всего два дополнительных символа! (полное раскрытие, я автор)

from fyeah import f

print(f("""'{'"all" the quotes'}'"""))

Есть много различий между f-строками и вызовами формата, вот, вероятно, неполный список.

  • f-строки позволяют произвольно оценивать код Python
  • f-строки не могут содержать обратную косую черту в выражении (поскольку форматированные строки не имеют выражения, поэтому я полагаю, вы могли бы сказать, что это не разница, но это отличается от того, что может сделать необработанный eval())
  • поиск dict в отформатированных строках не должен заключаться в кавычки. Поиск dict в f-строках можно заключать в кавычки, поэтому также можно искать нестроковые ключи.
  • f-строки имеют формат отладки, которого нет у format(): f"The argument is {spam=}"
  • выражения f-строки не могут быть пустыми

Предложения по использованию eval обеспечат вам полную поддержку формата f-строки, но они не работают со всеми типами строк.

def f_template(the_string):
    return eval(f"f'{the_string}'")

print(f_template('some "quoted" string'))
print(f_template("some 'quoted' string"))
some "quoted" string
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f_template
  File "<string>", line 1
    f'some 'quoted' string'
            ^
SyntaxError: invalid syntax

В этом примере также в некоторых случаях будет неправильно определена область видимости переменных.

person ucodery    schedule 24.06.2021
comment
Вау, супер. Работает "из коробки". Снимаю шляпу перед этим человеком с 11 повторениями! Как и твой список отличий, внушает доверие. Какие-нибудь ошибки, с которыми вы столкнулись? Я вижу, вы разработали (небольшой) набор тестов. Честно говоря, я понятия не имею, что вы делаете в своем файле c (_cfyeah.c)... но похоже, что вы знаете, что делаете. - person mike rodent; 17.07.2021
comment
Привет спасибо! Определенно пытался сделать его простым в использовании, так что это приятно слышать. _cfyeah.c предоставляет собственный CPython fstring eval, который не является частью общедоступного API Python. Это не обязательно для пакета, но обеспечивает значительное ускорение при использовании по сравнению с компиляцией строки каждый раз. - person ucodery; 20.07.2021

Предложение, использующее f-строки. Сделайте свою оценку на логическом уровне, где происходит шаблонизация, и передайте ее как генератор. Вы можете раскрутить его в любой точке, которую вы выберете, используя f-strings.

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  
person Ron Lawhorn    schedule 05.06.2018