如何评估变量作为F弦



我想拥有一种机制,该机制评估了一个F弦,其中在变量内提供了要评估的内容。例如,

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

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

在生产中使用eval通常是非常糟糕的实践,但有明显的例外。例如,用户可能是Python开发人员,在本地计算机上工作,他们想使用完整的Python语法来开发SQL查询。

重复:这里和这里有类似的问题。第一个问题是在模板的有限上下文中提出的。第二个问题虽然与这个问题非常相似,但已被标记为重复。因为这个问题的上下文与第一个问题明显不同,所以我决定根据第二个问题自动生成的建议提出第三个问题:

这个问题以前已经提出过,已经有了答案。如果 这些答案无法完全解决您的问题,请询问一个新的 问题。

即使在受信任的用户中,使用 eval应该是最后的度假胜地。

如果您愿意为更多的安全性和控制牺牲自己的语法灵活性,那么您可以使用str.format并提供整个范围。

这将不允许对表达式进行评估,但是单个变量将被形成到输出中。

代码

x = 3
y = 'foo'
s = input('> ')
print(s.format(**vars()))

示例

> {x} and {y}
3 and foo

这是我对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'xny'))

产生

x
y

print(fstr_eval(r'xny', raw_string=True))

产生

xny

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

最新更新