如何将变量评估为Python f字符串

时间:2019-02-14 23:48:38

标签: python python-3.x python-3.6 string-interpolation f-string

我想拥有一种评估f字符串的机制,其中要评估的内容位于变量内部。例如,

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

对于我想到的使用情况,字符串s可能来自用户输入(其中eval信任用户)。

虽然在生产中使用eval通常是非常不好的做法,但也有一些例外。例如,用户可能是在本地计算机上工作的Python开发人员,他想使用完整的Python语法来开发SQL查询。

重复说明:还有类似的问题herehere。在模板的有限上下文中提出了第一个问题。第二个问题尽管与此非常相似,但已被标记为重复。由于该问题的上下文与第一个问题大不相同,因此我决定根据第二个问题之后自动生成的建议来询问第三个问题:

  

此问题已被问过,并且已经有答案。如果   这些答案不能完全解决您的问题,请重新询问   问题。

2 个答案:

答案 0 :(得分:3)

即使对于受信任的用户,使用eval也仅是最后的选择。

如果您愿意牺牲语法的灵活性来获得更多的安全性和控制力,则可以使用str.format并为其提供整个范围。

这将不允许对表达式求值,但是单个变量将被格式化为输出。

代码

x = 3
y = 'foo'

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

示例

> {x} and {y}
3 and foo

答案 1 :(得分:0)

kadee's elegant answer的启发下,我尝试对f弦进行更可靠的评估。

但是,我想避免使用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

或更普遍地说,例如,将适当的globalslocals参数传递给eval

fstr_eval('{x+x}', eval=lambda expr: eval(expr, {}, {'x': 7})) # 14

我还提供了一种机制,该机制通过“原始字符串文字”机制选择是否应将\视为转义字符。例如,

print(fstr_eval(r'x\ny'))

收益

x
y

同时

print(fstr_eval(r'x\ny', raw_string=True))

收益

x\ny

我可能还没有注意到其他陷阱,但是出于许多目的,我认为这已经足够了。