我有一个用户输入功能的程序,例如sin(x)+1
。我正在使用ast
尝试通过将组件列入白名单来确定字符串是否“安全”,如this answer所示。现在我想解析字符串,在没有它们的系数之间添加乘法(*
)符号。
例如:
3x
- > 3*x
4(x+5)
- > 4*(x+5)
sin(3x)(4)
- > sin(3x)*(4)
(sin
已经在全局中,否则这将是s*i*n*(3x)*(4)
有没有有效的算法来实现这一目标?我更喜欢pythonic解决方案(即不是复杂的正则表达式,不是因为它们是pythonic,而是因为我不理解它们并想要一个我能理解的解决方案。简单的正则表达式是可以的。)
我非常愿意在一个条件下使用sympy
(对于这类事情看起来很容易):安全。显然sympy
使用了eval
。我目前的(部分)解决方案的安全性非常好。如果有人能够通过不受信任的输入使sympy
更安全,我也欢迎这一点。
答案 0 :(得分:7)
正则表达式是在vanilla python中完成工作的最快捷,最简洁的方式,我甚至会为你解释正则表达式,因为正则表达式是一个非常好的理解工具。
要完成目标,请使用以下语句:
import re
# <code goes here, set 'thefunction' variable to be the string you're parsing>
re.sub(r"((?:\d+)|(?:[a-zA-Z]\w*\(\w+\)))((?:[a-zA-Z]\w*)|\()", r"\1*\2", thefunction)
我知道它有点冗长而复杂,但是一个不同的,更简单的解决方案并没有立即显而易见,没有比这里进入正则表达式的更多hacky东西。但是,这已针对所有三个测试用例进行了测试,并且可以按照您的需要进行测试。
作为对此处发生的事情的简要说明:re.sub
的第一个参数是正则表达式,它匹配某个模式。第二个是我们用它替换它的东西,第三个是替换它的实际字符串。每当我们的正则表达式看到一个匹配时,它会删除它并插入替换,有一些特殊的背后 - 场景技巧。
对正则表达式进行更深入的分析:
((?:\d+)|(?:[a-zA-Z]\w*\(\w+\)))((?:[a-zA-Z]\w*)|\()
:匹配数字或函数调用,后跟变量或括号。
((?:\d+)|(?:[a-zA-Z]\w*\(\w+\)))
:第1组。注意:括号分隔一个组,这是一个子正则表达式。捕获组被索引以供将来参考;也可以使用修饰符重复组(稍后描述)。该组匹配数字或函数调用。
(?:\d+)
:非捕获组。紧接在左括号之后的任何具有?:
的组都不会为自己分配索引,但仍然充当&#34;部分&#34;的模式。防爆。 A(?:bc)+
将匹配&#34; Abcbcbccc ...&#34;等等,但你不能访问&#34; bcbcbccc&#34;与索引匹配。然而,没有这个小组,写作&#34; Abc +&#34;会匹配&#34; Abcccccccc ......&#34;
\d
:匹配任何数字一次。 \d
所有自己的正则表达式将分别匹配"1"
的{{1}},"2"
和"3"
。"123"
:匹配前一个元素一次或多次次。在这种情况下,前一个元素是+
,任意数字。在前面的示例中,\d
on&#34; 123&#34;将成功匹配&#34; 123&#34;作为一个单一的元素。这对我们的正则表达式至关重要,以确保正确注册多位数字。\d+
:管道字符,在正则表达式中,它有效地表示|
:or
将匹配"a|b"
或"a"
。在这种情况下,它分离了一个数字&#34;和#34;一个函数调用&#34 ;;匹配数字或函数调用。"b"
:匹配函数调用。也是一个非捕获组,如(?:[a-zA-Z]\w*\(\w+\))
。
(?:\d+)
:匹配函数调用的第一个字母。这没有修饰符,因为我们只需要确保第一个字符是一个字母; [a-zA-Z]
在技术上是一个有效的函数名称。A123
:匹配任何字母数字字符或下划线。确保第一个字母后,以下字符可以是字母,数字或下划线,仍然可以作为函数名称使用。\w
:匹配前一个元素 0或更多次。虽然最初看似不必要,但明星角色有效地使元素可选。在这种情况下,我们的修改元素是*
,但是一个函数在技术上不需要任何一个以上的字符; \w
是有效的函数名称。 A()
会匹配A
,因此不需要[a-zA-Z]
。在光谱的另一端,跟随第一个字母可能有任意数量的字符,这就是我们需要这个修饰符的原因。\w
:理解这一点很重要:这不是另一个群组。这里的反斜杠很像普通字符串中的转义字符。在正则表达式中,只要您使用反斜杠为特殊字符(例如括号,\(
或+
作序言,它就像普通字符一样使用它。对于函数的实际函数调用部分,*
匹配左括号。\(
:匹配数字,字母或下划线一次或多次。这可以确保函数实际上有一个参数进入它。\w+
:与\)
类似,但匹配结束括号\(
:第2组。匹配变量或左括号。
((?:[a-zA-Z]\w*)|\()
:匹配变量。这与我们的函数名称匹配器完全相同。但请注意,这是在非捕获组中:这很重要,因为OR检查的方式。紧随其后的OR整体上看这个组。如果没有分组,那么&#34;最后一个对象匹配&#34;将是(?:[a-zA-Z]\w*)
,这对我们想要的东西来说是不够的。它会说:&#34;匹配一个字母后跟更多字母或一个字母后跟括号&#34;。将此元素放在非捕获组中允许我们控制OR注册的内容。\w*
:或者角色。匹配|
或(?:[a-zA-Z]\w*)
。\(
:匹配左括号。一旦我们检查了是否有一个左括号,我们就不需要为了我们的正则表达式而检查它之外的任何内容。现在,请记住我们的两个小组,第一组和第二组?这些用在替换字符串\(
中。替换字符串不是真正的正则表达式,但它仍然具有某些特殊字符。在这种情况下,"\1*\2"
将插入该号码的组。所以我们的替换字符串是:&#34;将组1放入(这是我们的函数调用或我们的数字),然后放入星号(*),然后放入我们的第二组(变量或括号) &#34;
我认为总结一下!