Как оценить переменную как строку Python - PullRequest
0 голосов
/ 15 февраля 2019

Я хотел бы иметь механизм, который оценивает f-строку, где содержимое для оценки предоставляется внутри переменной.Например,

x=7
s='{x+x}'
fstr_eval(s)

Для случая использования, который я имею в виду, строка s может быть получена из пользовательского ввода (где пользователю доверяют с eval).

Хотяиспользование eval в производстве, как правило, очень плохая практика, есть заметные исключения.Например, пользователь может быть разработчиком Python, работающим на локальной машине, который хотел бы использовать полный синтаксис Python для разработки SQL-запросов.

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

Этот вопрос был задан ранее и уже имеетответ.Если эти ответы не полностью отвечают на ваш вопрос, пожалуйста, задайте новый вопрос.

Ответы [ 2 ]

0 голосов
/ 15 февраля 2019

Даже с доверенным пользователем, использование eval должно быть только последним средством.

Если вы готовы пожертвовать гибкостью своего синтаксиса для большей безопасности и контроля, тогда вы можете использовать str.format и предоставьте всю вашу область.

Это запретит оценку выражений, но в вывод будут отформатированы отдельные переменные.

Код

x = 3
y = 'foo'

s = input('> ')
print(s.format(**vars()))

Пример

> {x} and {y}
3 and foo
0 голосов
/ 15 февраля 2019

Вот моя попытка более надежной оценки 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

Вероятно, есть и другие подводные камни, которые у меня естьне заметил, но для многих целей я думаю, что этого будет достаточно.

...