1

我正在尝试在 python 中使用正则表达式来匹配图像序列中图像文件的帧号组件。我想提出一个涵盖许多不同命名约定的解决方案。如果我把它变成文字,我试图匹配两个点之间的一个或多个数字的最后一个实例(例如 0.0100。)。以下是我当前逻辑如何下降的示例:

import os
import re    

def sub_frame_number_for_frame_token(path, token='@'):
    folder = os.path.dirname(path)
    name = os.path.basename(path)
    pattern = r'\.(\d+)\.'
    matches = list(re.finditer(pattern, name) or [])
    if not matches:
        return path

    # Get last match.
    match = matches[-1]
    frame_token = token * len(match.group(1))
    start, end = match.span()
    apetail_name = '%s.%s.%s' % (name[:start], frame_token, name[end:])
    return os.path.join(folder, apetail_name)

# Success
eg1 = 'xx01_010_animation.0100.exr'
eg1 = sub_frame_number_for_frame_token(eg1) # result: xx01_010_animation.@@@@.exr

# Failure
eg2 = 'xx01_010_animation.123.0100.exr'
eg2 = sub_frame_number_for_frame_token(eg2) # result: xx01_010_animation.@@@.0100.exr

我意识到还有其他方法可以解决这个问题(我已经实施了一个解决方案,我在点处分割路径并采用最后一项是数字)但我借此机会学习一些关于常规的知识表达式。看起来正则表达式从左到右创建组,并且不能在模式中多次使用字符。首先,无论如何要从右到左搜索字符串?其次,为什么模式没有在 eg2 中找到两个匹配项(123 和 0100)?

干杯

4

4 回答 4

2

finditer将返回一个迭代器“在字符串中的所有非重叠匹配”。

在您的示例中,.第一个匹配的最后一个将“消耗”.第二个匹配的第一个。基本上,在进行第一次匹配之后,您eg2示例的剩余字符串是0100.exr,它不匹配。

为避免这种情况,您可以使用前瞻断言( ?=),它不会消耗第一个匹配项:

>>> pattern = re.compile(r'\.(\d+)(?=\.)')

>>> pattern.findall(eg1)
['0100']

>>> pattern.findall(eg2)
['123', '0100']

>>> eg3 = 'xx01_010_animation.123.0100.500.9000.1234.exr'
>>> pattern.findall(eg3)
['123', '0100', '500', '9000', '1234']
# and "right to left"
>>> pattern.findall(eg3)[::-1]
['1234', '9000', '500', '0100', '123']
于 2013-09-12T01:55:25.787 回答
1

我的解决方案使用了一种非常简单的hackish 方式来修复它。它反转函数开头的字符串path并反转函数末尾的返回值。它基本上使用正则表达式来搜索给定字符串的向后版本。黑客,但它的工作原理。我使用这个问题中显示的语法来反转字符串。

import os
import re    

def sub_frame_number_for_frame_token(path, token='@'):
    path = path[::-1]
    folder = os.path.dirname(path)
    name = os.path.basename(path)
    pattern = r'\.(\d+)\.'
    matches = list(re.finditer(pattern, name) or [])
    if not matches:
        return path

    # Get last match.
    match = matches[-1]
    frame_token = token * len(match.group(1))
    start, end = match.span()
    apetail_name = '%s.%s.%s' % (name[:start], frame_token, name[end:])
    return os.path.join(folder, apetail_name)[::-1]

# Success
eg1 = 'xx01_010_animation.0100.exr'
eg1 = sub_frame_number_for_frame_token(eg1) # result: xx01_010_animation.@@@@.exr

# Failure
eg2 = 'xx01_010_animation.123.0100.exr'
eg2 = sub_frame_number_for_frame_token(eg2) # result: xx01_010_animation.123.@@@@.exr

print(eg1)
print(eg2)
于 2013-09-12T02:01:09.710 回答
0

如果您只关心最后一个 \.(\d+)\.,则从字符串末尾锚定您的模式并执行简单的 re.search(_):
\.(\d+)\.(?:.*?)$
where (?:.*?)is non-capturing and non-greedy,因此它将消耗尽可能少的字符你真正的目标和字符串的结尾,这些字符不会出现在matches.
(警告 1:我还没有测试过这个。警告 2:这是一个丑陋的正则表达式,所以添加一个注释来解释它在做什么。)
更新:实际上我猜你可以只做 a^.*(\.\d\.)并让隐式贪婪.*匹配尽可能(包括字符串中较早出现的匹配项),同时仍与您的组匹配。这使得正则表达式更简单,但我认为它会让你的意图不太清楚。

于 2013-09-12T02:21:09.433 回答
0

我认为问题在于 finditer 只返回不重叠的匹配项。因为两者都是'。字符是正则表达式的一部分,它不会将第二个点视为另一个匹配的可能开始。您可能可以使用前瞻构造 ?= 来匹配第二个点,而无需将其与“?=.”一起使用。

由于正则表达式的工作方式,我认为没有一种从右到左搜索的简单方法(尽管我想你可以反转字符串并将模式倒写......)。

于 2013-09-12T02:00:14.427 回答