Вот моя попытка более надежной оценки f-струн, вдохновленная элегантным ответом kadee на аналогичный вопрос.
Однако я хотел бы избежать некоторых основных ошибок eval
подход.Например, eval(f"f'{template}'")
терпит неудачу всякий раз, когда шаблон содержит апостроф, например, the string's evaluation
становится f'the string's evaluation'
, что приводит к синтаксической ошибке.Первое улучшение заключается в использовании тройных апострофов:
eval(f"f'''{template}'''")
Теперь (главным образом) безопасно использовать апострофы в шаблоне, если они не являются тройными апострофами.(Однако с тройными кавычками все в порядке.) Заметным исключением является апостроф в конце строки: whatcha doin'
становится f'''whatcha doin''''
, что приводит к синтаксической ошибке в четвертом последовательном апострофе.Следующий код позволяет избежать этой конкретной проблемы, удаляя апострофы в конце строки и возвращая их после оценки.
import builtins
def fstr_eval(_s: str, raw_string=False, eval=builtins.eval):
r"""str: Evaluate a string as an f-string literal.
Args:
_s (str): The string to evaluate.
raw_string (bool, optional): Evaluate as a raw literal
(don't escape \). Defaults to False.
eval (callable, optional): Evaluation function. Defaults
to Python's builtin eval.
Raises:
ValueError: Triple-apostrophes ''' are forbidden.
"""
# Prefix all local variables with _ to reduce collisions in case
# eval is called in the local namespace.
_TA = "'''" # triple-apostrophes constant, for readability
if _TA in _s:
raise ValueError("Triple-apostrophes ''' are forbidden. " + \
'Consider using """ instead.')
# Strip apostrophes from the end of _s and store them in _ra.
# There are at most two since triple-apostrophes are forbidden.
if _s.endswith("''"):
_ra = "''"
_s = _s[:-2]
elif _s.endswith("'"):
_ra = "'"
_s = _s[:-1]
else:
_ra = ""
# Now the last character of s (if it exists) is guaranteed
# not to be an apostrophe.
_prefix = 'rf' if raw_string else 'f'
return eval(_prefix + _TA + _s + _TA) + _ra
Без указания функции оценки доступны локальные переменные этой функции, поэтому
print(fstr_eval(r"raw_string: {raw_string}\neval: {eval}\n_s: {_s}"))
печатает
raw_string: False
eval: <built-in function eval>
_s: raw_string: {raw_string}\neval: {eval}\n_s: {_s}
, а префикс _
уменьшаетВероятность непреднамеренных столкновений позволяет избежать этой проблемы, передав соответствующую функцию оценки.Например, можно передать текущее глобальное пространство имен с помощью lambda
:
fstr_eval('{_s}', eval=lambda expr: eval(expr))#NameError: name '_s' is not defined
или, в более общем случае, передав подходящие аргументы globals
и locals
в eval
, например
fstr_eval('{x+x}', eval=lambda expr: eval(expr, {}, {'x': 7})) # 14
Я также включил механизм выбора того, должен ли \
рассматриваться как escape-символ с помощью механизма "raw string literal".Например,
print(fstr_eval(r'x\ny'))
дает
x
y
, а
print(fstr_eval(r'x\ny', raw_string=True))
дает
x\ny
Вероятно, есть и другие подводные камни, которые у меня естьне заметил, но для многих целей я думаю, что этого будет достаточно.