使用Python从字典的键动态生成正则表达式

3
def t_FUNC_(self, t):
        r'(?i)I|(?i)J|(?i)K|(?i)L|(?i)M|(?i)N|(?i)Y'
        return t

在上面的函数中,我正在返回一个正则表达式,这意味着 FUNC 可以是 I 或 J 或 K 或 L 或 M 或 N 或 Y。
现在,我有一个像这样的字典:
dic = { 'k1':'v1', 'k2':'v2' }

我在上述函数中可以访问这个字典。如何根据字典的键动态生成正则表达式。字典的大小也不固定。

所以,我想用类似 r'(?i)k1|(?i)k2' 的东西来替换 r'(?i)I|(?i)J|(?i)K|(?i)L|(?i)M|(?i)N|(?i)Y'

附注:上面的模式代码用于使用 Python 中的 Ply 库编写词法分析器时生成令牌。


2
t_FUNC_ 返回它的第二个参数。它不返回正则表达式。 - DYZ
@DYZ:这就是PLY的工作方式。正则表达式取自函数的文档字符串,并且只有在匹配到正则表达式(仅当匹配到正则表达式时)后才调用该函数。操作函数的第二个参数 - 实际上是第一个参数,因为OP使用了词法分析器类 - 是扫描器已经构建的标记对象;其想法是操作函数可以在将标记对象传递给解析器之前根据需要修改标记对象。它甚至可以通过返回与其所给参数不同的内容来制造全新的标记对象。 - rici
@rici 我也是这么想的。但是,与 OP 的说法相反,该函数仍然没有返回正则表达式。 - DYZ
1
@dyz: 确实如此,但我强烈怀疑这只是简单的措辞错误。我怀疑OP并非以英语为母语。请参阅他们之前的问题https://stackoverflow.com/questions/54048095/getter-setter-as-function-in-python-class-giving-no-attribute-found-error ,它稍微清晰一些,但仍需要有点包容心地阅读。 - rici
3个回答

2
将字典的键放入正则表达式中非常简单:

代码:

regex = '|'.join('(?i){}'.format(k) for k in data)

测试代码:

data = {'k1': 'v1', 'k2': 'v2'}
regex = '|'.join('(?i){}'.format(k) for k in data)
print(regex)

结果:

(?i)k1|(?i)k2

3
确定只需要在模式开头加上一次(?i)就可以了。 - CertainPerformance
2
(?i) 开启不区分大小写模式。 - DYZ
这不适用于Ply。至少,不使用编写整个自定义词法分析器是无法实现的。 - rici
@rici,你能否请写一个例子? - zubug55
3
PLY 希望通过分析符合命名规则的函数的doc注释来生成静态词法分析器。您正在尝试在调用函数时动态生成正则表达式,这太晚了,无法与 PLY 一起使用。请参阅 PLY 的“t_id”文档以获取一个可能的解决方案。 - aghast
显示剩余2条评论

1
'(?i)'+'|'.join(re.escape(k) for k in dic)

你需要使用re.escape,以防万一dic键中包含正则表达式语言中的控制字符(如|)。此外,全局内联标志的使用,例如(?i)已经被弃用,除了在模式的开头。如果你只想将其应用于表达式的一部分,可以使用新的本地标志语法,(?i:foo)

原始问题是关于使用 [ply] 生成词法分析器。由于 Ply 不允许在词法扫描器生成后动态修改正则表达式,因此这个答案与实际问题并不特别相关。 - rici

1
如评论中@AustinHastings所说,Ply通过将词法分析器类中提供的正则表达式组合来构建词法扫描器,这些正则表达式可以是类成员的值,也可以是类成员函数的文档字符串。一旦扫描器被构建,它就不会被修改,因此在生成扫描器之后,您确实无法动态调整正则表达式。
然而,对于您考虑的特定应用程序,创建自定义正则表达式并不是必需的。您可以使用Ply手册中演示的简单得多的过程,该过程显示了如何识别保留字而无需为每个单词创建自定义正则表达式。
这个想法真的很简单。保留字(在您的情况下是函数名)通常是已经在词法分析器中使用的某些更一般模式的具体示例。这几乎肯定是这种情况,因为词法分析器必须以某种方式识别每个标记,因此在将动态生成的单词添加到扫描器之前,它必须被识别为其他东西。与其尝试覆盖特定实例的其他模式,我们只需让标记被识别,然后在返回标记之前更正其类型(以及可能的值)。
这是从Ply手册中稍作修改的示例版本:
def t_ID(t):
     r'[a-zA-Z_][a-zA-Z_0-9]*'
     # Apparently case insensitive recognition is desired, so we use
     # the lower-case version of the token as a lookup key. This means
     # that all the keys in the dictionary must be in lower-case
     token = t.value.lower()
     if token in self.funcs:
         t.type = 'FUNC'
     return t

您可能希望调整上面的内容,以便对与funcs字典中键相关联的值执行某些操作,尽管这可以在语义分析期间稍后完成。

由于funcs字典在词法分析器(或解析器)的生成过程中并不以任何方式参与,因此不需要特别聪明才能将其传递到Lexer对象中。实际上,它甚至不需要在词法分析器对象中;您可以在构造词法分析器对象时将解析器对象添加到词法分析器对象中,从而使您可以将字典放入解析器对象中,使其更易于被解析器动作访问。

这比尝试构建自定义正则表达式的一个原因是,它不会识别作为非保留字前缀的保留字。例如,如果cos是其中之一函数,并且您已经成功生成了相应的内容

t_ID = r'[a-zA-Z_][a-zA-Z_0-9]*'
def t_FUNC(t):
    r'(?i)sin|cos|tan'
    # do something

然后您会发现:
cost = 3

被扫描为FUNC(cos), ID(t), '=', NUMBER(3),几乎肯定不是您想要的。将逻辑放在t_ID函数内部完全避免了这个问题,因为只有完整的标记才会被考虑。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接