Як відкласти / відкласти оцінку f-рядків?


99

Я використовую рядки шаблонів для генерації деяких файлів, і мені подобається лаконічність нових 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}

5
Цього не можна зробити за допомогою fрядка. fРядок не дані, і це, безумовно , не є рядок; це код. (Перевірте це за допомогою disмодуля.) Якщо ви хочете, щоб код був оцінений пізніше, ви використовуєте функцію.
kindall

12
FYI, PEP 501 запропонував функцію, близьку до вашого першого ідеалу, але в даний час вона "відкладена в очікуванні подальшого досвіду роботи з [f-рядками]".
jwodder

Шаблон - це статичний рядок, але f-рядок - це не рядок, це об’єкт коду, як сказав @kindall. Я думаю, що f-рядок прив'язується до змінних негайно, коли він створюється (у Python 3.6,7), а не коли він врешті використовується. Тож f-string може бути менш корисним, ніж ваш потворний старий .format(**locals()), хоча і косметично приємніший. Поки не буде застосовано PEP-501.
smci

Гвідо врятуй нас, але PEP 498 насправді це зіпсував . Відкладена оцінка, описана в PEP 501, абсолютно повинна була бути включена в основну реалізацію f-string. Тепер ми залишаємося торгуватися між менш функціональним, надзвичайно повільним str.format()методом, що підтримує відкладене оцінювання, з одного боку, та більш функціональним, надзвичайно швидким синтаксисом f-рядка, який не підтримує відкладене оцінювання, з іншого. Отже, нам все ще потрібні обидва, і Python досі не має стандартного форматування рядків. Вставити мем стандарту xkcd.
Сесіл Каррі

Відповіді:


26

Ось повний "Ідеал 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

13
Я прийму це як відповідь, хоча я не думаю, що коли-небудь буду використовувати його в коді через надзвичайну кмітливість. Ну, можливо, ніколи :). Можливо, люди python можуть використовувати його для реалізації PEP 501 . Якби мої запитання полягали в тому, "як мені впоратися з цим сценарієм", відповідь буде "просто продовжуйте використовувати функцію .format () і чекайте, поки PEP 501 вирішить." Дякуємо, що придумали, як робити те, чого не слід робити, @PaulPanzer
JDAnders

6
Це не працює, коли шаблон містить щось складніше, ніж прості імена змінних. Наприклад: template = "The beginning of the name is {name[:4]}"(-> TypeError: string indices must be integers)
близько

6
@bli Цікаво, здається, це обмеження str.format. Раніше я думав, що f-рядки - це просто синтаксичний цукор для чогось подібного, str.format(**locals(), **globals())але, очевидно, я помилявся.
Paul Panzer,

4
Будь ласка, не використовуйте це у виробництві. inspectє червоним прапором.
alexandernst

1
У мене є 2 запитання, чому перевірка "червоного прапора" на виробництво може бути такою справою як виняток чи існуватимуть більш життєздатні обхідні шляхи? І чи є щось проти використання __slots__тут для зменшеного використання пам'яті?
Яб,

21

Це означає, що шаблон є статичним рядком із тегами форматування

Так, саме тому у нас є літерали із полями заміни, і .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.


Смішно, я розмістив точно такий же фрагмент. Але я відкликав його через обмеження масштабу. (Спробуйте обернути цикл for у функцію.)
Paul Panzer

@PaulPanzer, можливо, ти хочеш відредагувати питання та включити його знову? Я б не проти видалити відповідь. Це життєздатна альтернатива для справи ОП, вона не є життєздатною альтернативою для всіх випадків, оскільки вона підступна.
Dimitris Fasarakis Hilliard

1
Ні, добре, тримай. Я набагато щасливіший від свого нового рішення. Але я бачу вашу думку, що це життєздатне, якщо ви знаєте про його обмеження. Можливо, ви можете додати невелике застереження до свого допису, щоб ніхто не міг стріляти ногою, неправильно використовуючи його?
Paul Panzer

16

Стислий спосіб отримання рядка, що оцінюється як 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

4
безумовно найкраща відповідь! як вони не включили цю просту реалізацію як вбудовану функцію, коли ввели f-рядки?
user3204459

1
ні, що втрачає область застосування. Єдина причина, яка працює, полягає в тому, що nameє глобальною. f-рядки повинні бути відкладені в обчисленні, але клас FString повинен створити список посилань на аргументи, що обробляються, переглянувши місцеві абоненти абонентів, що викликають, і потім оцінити рядок, коли він використовується.
Ерік Аронесті

2
@ user3204459: Оскільки можливість виконувати довільні рядки за своєю суттю є загрозою безпеці - саме тому використання, eval()як правило, не рекомендується.
Мартіно

2
@martineau, це повинно було бути властивістю python, щоб вам не потрібно було використовувати eval ... плюс, f-string має ті самі ризики, що і eval (), оскільки ви можете помістити що-небудь у фігурні дужки, включаючи шкідливий код, так що якщо це тоді концерн не використовує f-рядків
user3204459

2
Це саме те, що я шукав, качачись на 'fstr відкласти ". Евал здається не гіршим за використання fstrings загалом, оскільки вони, я думаю, обидва мають однакову силу: f" {eval (' print (42) ')} "
user2692263

12

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
Я дійсно не бачу переваг у вашому фрагменті коду. Я маю на увазі, ви завжди можете писати просто The current name is {name}всередині template.txtфайлу, а потім використовувати print(template_a.format(name=name))(або .format(**locals())). Код довший приблизно на 10 символів, але через це він не вводить жодних можливих проблем із безпекою eval.
Бакуріу

@Bakuriu - Так; як я вже сказав, хоча evalце дозволяє нам писати f'{name}'та відкладати оцінку, nameпоки не буде бажано, це поступається простому створенню звичайного рядка шаблону, а потім його виклику format, як це вже робив OP.
TigerhawkT3,

4
"F-рядок - це просто більш стислий спосіб створення відформатованого рядка, замінивши .format (** імена) на f." Не зовсім - вони використовують різний синтаксис. У мене немає недавно достатнього python3 для перевірки, але, наприклад, я вважаю, що f '{a + b}' працює, тоді як '{a + b}'. Формат (a = a, b = b) викликає KeyError . .format (), мабуть, чудово в багатьох контекстах, але це не заміна.
philh

2
@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.
поблизу

12

Використання .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 потребував би явного перемикання поведінки. Може, використовувати букву 'g'? ;)


Погодьтеся з цією відповіддю від усього серця. Цей варіант використання - це те, про що я думав під час пошуку цього питання.
justhalf

Це правильна відповідь. Деякі терміни: %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)
Jaleks

8

Те, що ви хочете, вважається вдосконаленням 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

7

натхненний відповіддю 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)

саме про це і ставилося запитання


4

Або, можливо, не використовуйте 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
бли

Але це не працює і в звичайному використанні, будь ласка , подивіться на відповідь stackoverflow.com/questions/14072810 / ...
msztolcman


0

Пропозиція, яка використовує 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  
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.