将ruby正则表达式定义转换为python正则表达式

时间:2017-06-11 14:03:24

标签: ruby regex

我遵循为在Gemfile中捕获gem名称而定义的正则表达式。

GEM_NAME = /[a-zA-Z0-9\-_\.]+/

QUOTED_GEM_NAME = /(?:(?<gq>["'])(?<name>#{GEM_NAME})\k<gq>|%q<(?<name>#{GEM_NAME})>)/

我想将它们转换为可以在python和其他语言中使用的正则表达式。

我尝试(?:(["'])([a-zA-Z0-9\-_\.]+)\k["']|%q<([a-zA-Z0-9\-_\.]+)>)基于替换和几个类似的组合,但没有一个工作。这是regexr链接http://regexr.com/3g527

有人可以解释一下将这些ruby正则表达式定义转换为python可以使用的形式的正确过程。

3 个答案:

答案 0 :(得分:1)

要定义命名组,您需要使用(?P<name>),然后使用(?p=name)命名 如果你能买得起第三方库,你可以使用PyPi正则表达式模块并使用你在Ruby中使用的方法(因为regex支持多个同名的捕获组):

s = """%q<Some-name1> "some-name2" 'some-name3'"""

GEM_NAME = r'[a-zA-Z0-9_.-]+'
QUOTED_GEM_NAME = r'(?:(?P<gq>["\'])(?<name>{0})(?P=gq)|%q<(?P<name>{0})>)'.format(GEM_NAME)
print(QUOTED_GEM_NAME)
# => # (?:(?P<gq>["\'])(?<name>[a-zA-Z0-9_.-]+)(?P=gq)|%q<(?P<name>[a-zA-Z0-9_.-]+)>)

import regex
res = [x.group("name") for x in regex.finditer(QUOTED_GEM_NAME, s)]
print(res)
# => ['Some-name1', 'some-name2', 'some-name3']

替换模式中的反向引用。

请参阅this Python demo

如果您决定使用Python re,则无法在一个正则表达式模式中处理具有相同名称的组。

您可以完全丢弃已命名的组并使用编号的组,并使用re.finditer迭代所有匹配并理解以获取正确的捕获。

Example Python code

import re
GEM_NAME = r'[a-zA-Z0-9_.-]+'
QUOTED_GEM_NAME = r"([\"'])({0})\1|%q<({0})>".format(GEM_NAME)
s = """%q<Some-name1> "some-name2" 'some-name3'"""
matches = [x.group(2) if x.group(1) else x.group(3) for x in re.finditer(QUOTED_GEM_NAME, s)]
print(matches)
# => ['Some-name1', 'some-name2', 'some-name3']

因此,([\"'])({0})\1|%q<({0})>有3个捕获组:如果组1匹配,则第一个替代匹配,因此,组2被采用,否则,第二个替代匹配,并且在理解中抓取组3值

模式详情

  • ([\"']) - 第1组:"'
  • ({0}) - 第2组:GEM_NAME模式
  • \1 - 对第1组捕获值的内联反向引用(请注意r'...'原始字符串文字允许使用单个反斜杠在字符串文字中定义反向引用)
  • | - 或
  • %q< - 文字子字符串
  • ({0}) - 第3组:GEM_NAME模式
  • > - 文字>

答案 1 :(得分:1)

您可以像这样重写您的模式:

GEM_NAME = r'[a-zA-Z0-9_.-]+'

QUOTED_GEM_NAME = r'''["'%] # first possible character
    (?:(?<=%)q<)? # if preceded by a % match "q<"
    (?P<name> # the three possibilities excluding the delimiters
        (?<=") {0} (?=") |
        (?<=') {0} (?=') |
        (?<=<) {0} (?=>)
    )
    ["'>] #'"# closing delimiter
    (?x) # switch the verbose mode on for all the pattern
'''.format(GEM_NAME)

demo

优点:

  • 模式不会以使搜索速度变慢的交替开始。 (此处的替换仅在报价或%之后的有趣位置进行测试,当您的版本测试字符串中每个位置的交替的每个分支时)。这种优化技术被称为“第一个字符识别”,它包括快速丢弃字符串中无用的位置。
  • 您只需要一个捕获组出现(引号和尖括号从中排除,仅使用外观进行测试)。通过这种方式,您可以使用re.findall获取宝石列表而无需进一步操作。
  • gq组无用且被删除(以创建无用捕获组为代价缩短模式不是一个好主意)

请注意,您无需转义字符类中的点。

答案 2 :(得分:1)

一种简单的方法是使用条件并合并名称。

(?:(?:(["'])|%q<)(?P<name>[a-zA-Z0-9\-_\.]+)(?(1)\1|>))

扩展

 (?:
      (?:                           # Delimiters
           ( ["'] )                      # (1), ' or "
        |                              # or,
           %q<                           # %q
      )
      (?P<name> [a-zA-Z0-9\-_\.]+ ) # (2), Name
      (?(1) \1 | > )                # Did group 1 match ? match it here, else >
 )

Python

import re

s = ' "asdf"  %q<asdfasdf>  '

print ( re.findall( r'(?:(?:(["\'])|%q<)(?P<name>[a-zA-Z0-9\-_\.]+)(?(1)\1|>))', s ) )

输出

[('"', 'asdf'), ('', 'asdfasdf')]