Подтвердить что ты не робот

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

Ответ 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