Як відкласти / відкласти оцінку 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.