Python相当于Perl匹配并在if块中捕获

时间:2015-02-04 01:02:05

标签: python regex

我正在慢慢地从Perl迁移到Python,并试图了解使用正则表达式的最佳实践。

我有以下Perl代码 - 这段代码基本上以字符串作为输入,并根据正则表达式匹配和捕获将重新排列的字符串作为输出吐出:

#!/usr/bin/env perl

use strict;
use warnings;

my $str = $ARGV[0] || die "Arg?";

my $result;

if($str =~ m/^\d{12}$/) {
    $result = $str;
} elsif($str =~ m{^(\d{2})/(\d{2})/(\d{4})$}) {
    $result = "${1}${2}0000${3}";
} elsif($str =~ m{^(\d{4})$}) {
    $result = "01010000${1}";
} else {
    die "Invalid string";
}

print("Result: $result\n");

Python 3中有什么好处?

到目前为止,我提出了以下内容,但在elif部分匹配两次似乎效率低下。在开始时编译所有正则表达式也似乎效率低下。

#!/usr/bin/env python3

import re, sys

str = sys.argv[1]

p1 = re.compile('\d{12}')
p2 = re.compile('(\d{2})/(\d{2})/(\d{4})')
p3 = re.compile('(\d{4})')

if p1.match(str):
    result = str
elif p2.match(str):
    m = p2.match(str)
    result = '%s%s0000%s' % (m.group(1), m.group(2), m.group(3))
elif p3.match(str):
    m = p3.match(str)
    result = '01010000%s' % (m.group(1))
else:
    raise Exception('Invalid string')

print('Result: ' + result)

鉴于Python的座右铭"应该有一个 - 最好只有一个 - 明显的方式去做" - 关于这里最好的方法是什么的任何想法/建议?

提前感谢您提出任何建议。

祝你好运, -Pavel

2 个答案:

答案 0 :(得分:2)

关于您的代码的几点说明:

  1. 预编译的正则表达式
    如果您不打算重用它们,则无需显式编译正则表达式。通过使用模块级功能,您可以获得更清晰的代码:
    使用m = re.match(pattern, text)
    而不是p1 = re.compile(pattern)后跟m = p1.match(str)

  2. 尝试匹配(如果匹配) - 使用匹配的组格式输出
    Python正则表达式工具提供了一个完全适合您案例的函数:re.subn()。 它执行正则表达式替换并返回许多替换。

  3. 效果考虑因素

    • re.match()被调用两次 - 它将尝试匹配同一行两次并返回两个不同的匹配对象。它可能会花费你一些额外的周期。
    • re.compile()(或模块级匹配函数)调用两次 - 根据{{​​3}}:

      ,这没关系
        

      注意:传递给re.compile()的最新模式的编译版本和模块级匹配函数被缓存,因此一次只使用几个正则表达式的程序不必担心编译正则表达式

    •   
    • 如何避免正则表达式预编译
        代码定义了匹配输入字符串时应遵循的正则表达式顺序。只有在我们100%确定我们需要它时,编译正则表达式才有意义。见下面的代码。它比实际解释简单得多。
    •   
    • 过早优化
        您没有遇到任何性能问题,不是吗?通过尽早优化,您可能会花费一些时间而没有任何可观察到的影响。
    •   

  4. 座右铭:

    import re
    
    rules = ( (r'\d{12}', r'\g<0>')
            , (r'(\d{2})/(\d{2})/(\d{4})', r'\1\g<2>0000\3') 
            #using r'\1\20000\3' would imply group 1 followed by group 20000!
            , (r'(\d{4})', r'01010000\1') )
    
    def transform(text):
        for regex, repl in rules:
            # we're compiling only those regexes we really need
            result, n = re.subn(regex, repl, text)
            if n: return result
        raise ValueError('Invalid string')
    
    tests = ['1234', r'12/34/5678', '123456789012']
    for test in tests:
        print(transform(test))
    
    transform('this line supposed to trigger exception')
    

    希望这有帮助

答案 1 :(得分:1)

如果您绝对决定不再执行相同的正则表达式匹配,则可以执行以下操作:

p1 = re.compile('\d{12}')
p2 = re.compile('(\d{2})/(\d{2})/(\d{4})')
p3 = re.compile('(\d{4})')

# Functions to perform the processing steps required for each
# match- might be able to save some lines of code by making
# these lambdas
def result1(s, m):
    return s

def result2(s, m):
    return '%s%s0000%s' % (m.group(1), m.group(2), m.group(3))

def result3(s, m):
    return '01010000%s' % (m.group(1))

for pattern, result_getter in [(p1, result1), (p2, result2), (p3, result3)]:
    m = pattern.match(str)
    if m:
        result = result_getter(str, m)
        break

print('Result: ' + result)

我个人认为这种微观优化水平不会产生太大影响, 但有一种方法可以完成它。