我有一个程序,用户在其中输入一个函数,例如sin(x)+1
。我在用着ast
尝试通过将组件列入白名单来确定字符串是否“安全”,如下所示这个答案 https://stackoverflow.com/a/11952618/4414003。现在我想解析字符串以添加乘法(*
) 系数之间的符号没有它们。
例如:
-
3x
-> 3*x
-
4(x+5)
-> 4*(x+5)
-
sin(3x)(4)
-> sin(3x)*(4)
(sin
已经在全局变量中,否则这将是s*i*n*(3x)*(4)
有没有有效的算法来实现这一点?我更喜欢Python式的解决方案(即不是复杂的正则表达式,不是因为它们是Python式的,而是因为我也不理解它们并且想要一个我能理解的解决方案。简单的正则表达式就可以了。)
我非常愿意使用sympy
(对于这类事情来说,这看起来真的很容易)在一个条件下:安全。显然sympy
uses eval
在引擎盖下。我当前的(部分)解决方案具有很好的安全性。如果有人有办法制作sympy
使用不受信任的输入更安全,我也欢迎这一点。
正则表达式无疑是在普通 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)
我知道它有点长和复杂,但是一个不同的、更简单的解决方案不会立即变得显而易见,除非比这里的正则表达式中包含更多的黑客内容。但是,这已经针对您的所有三个测试用例进行了测试,并且完全符合您的要求。
作为对这里发生的事情的简要解释:第一个参数re.sub
是正则表达式,它匹配某种模式。第二个是我们要替换的内容,第三个是要替换的实际字符串。每当我们的正则表达式看到匹配项时,它就会删除它并插入替换,并使用一些特殊的幕后技巧。
对正则表达式的更深入分析如下:
-
((?:\d+)|(?:[a-zA-Z]\w*\(\w+\)))((?:[a-zA-Z]\w*)|\()
: Matches a number or a function call, followed by a variable or parentheses.
-
((?:\d+)|(?:[a-zA-Z]\w*\(\w+\)))
: Group 1. Note: Parentheses delimit a Group, which is sort of a sub-regex. Capturing groups are indexed for future reference; groups can also be repeated with modifiers (described later). This group matches a number or a function call.
-
(?:\d+)
: Non-capturing group. Any group with ?:
immediately after the opening parenthesis will not assign an index to itself, but still act as a "section" of the pattern. Ex. A(?:bc)+
will match "Abcbcbcbc..." and so on, but you cannot access the "bcbcbcbc" match with an index. However, without this group, writing "Abc+" would match "Abcccccccc..."
-
\d
:匹配任意数字一次。正则表达式为\d
它自己的所有内容将分别匹配,"1"
, "2"
, and "3"
of "123"
.
-
+
: 匹配前一个元素一个或多个次。在这种情况下,前一个元素是\d
, 任何数字。在前面的例子中,\d+
“123”将成功匹配“123”作为单个元素。这对于我们的正则表达式至关重要,以确保正确注册多位数字。
-
|
:管道字符,在正则表达式中,它有效地表示or
: "a|b"
将匹配"a"
OR "b"
。在这种情况下,它将“一个数字”和“一个函数调用”分开;匹配数字或函数调用。
-
(?:[a-zA-Z]\w*\(\w+\))
: Matches a function call. Also a non-capturing group, like (?:\d+)
.
-
[a-zA-Z]
: 匹配函数调用的第一个字母。对此没有任何修改,因为我们只需要确保first字符是一个字母;A123
从技术上讲是一个有效的函数名称。
-
\w
:匹配任何字母数字字符或下划线。确保第一个字母后,后面的字符可以是字母、数字或下划线,并且仍然作为函数名称有效。
-
*
: 匹配前一个元素0个或更多次。虽然最初看起来没有必要,但明星角色实际上是一个元素optional。在这种情况下,我们修改的元素是\w
,但从技术上讲,函数不需要超过一个字符;A()
是一个有效的函数名称。A
将匹配[a-zA-Z]
, 制作\w
不必要。另一方面,可以有任意数量的字符下列的第一个字母,这就是我们需要这个修饰符的原因。
-
\(
:理解这一点很重要:这不是另一个团体。这里的反斜杠的作用很像普通字符串中的转义字符。在正则表达式中,任何时候您在特殊字符(例如括号)前面添加+
, or *
带有反斜杠,它像普通字符一样使用它。\(
火柴左括号,用于函数的实际函数调用部分。
-
\w+
:匹配数字、字母或下划线一次或多次。这确保了该函数实际上有一个参数进入其中。
-
\)
: Like \(
,但匹配一个closing插入语
-
((?:[a-zA-Z]\w*)|\()
: Group 2. Matches a variable, or an opening parenthesis.
-
(?:[a-zA-Z]\w*)
: 匹配一个变量。这与我们的函数名称匹配器完全相同。但是,请注意,这是在非捕获组中:这很重要,因为 OR 检查的方式。紧随其后的 OR 将该组视为一个整体。如果没有分组,“最后一个匹配的对象”将是\w*
,这不足以满足我们的需求。它会说:“匹配一个字母后跟多个字母或一个字母后跟一个括号”。将此元素放入非捕获组中可以让我们控制 OR 寄存器的内容。
-
|
: 或者性格。火柴(?:[a-zA-Z]\w*)
or \(
.
-
\(
: 匹配左括号。一旦我们检查了是否有左括号,我们就不需要为了正则表达式的目的而检查它之外的任何内容。
现在,还记得我们的两组,第一组和第二组吗?这些用于替换字符串,"\1*\2"
。替换字符串不是真正的正则表达式,但它仍然具有某些特殊字符。在这种情况下,\<number>
将插入该号码的组。所以我们的替换字符串是这样的:“将第 1 组放入(我们的函数调用或我们的数字),然后放入星号 (*),然后放入我们的第二组(变量或括号)”
我想这就是总结吧!
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)