Python中类似于Perl的if块内匹配和捕获的等效方法

4

我正在逐渐从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


你不能将赋值语句作为条件。请注意,条件表达式应该是一个布尔值。 - Joran Beasley
将你的正则表达式定义为原始字符串。 - Avinash Raj
@JoranBeasley - 我正在尝试找出在Python中完成这个任务的最佳方法,避免我代码中存在的低效率。 - Pavel Chernikov
@AvinashRaj - 抱歉,不清楚那会如何有所帮助? - Pavel Chernikov
对于那些使用Python 3.8的人,请注意:可以使用赋值表达式,参见https://dev59.com/1XVD5IYBdhLWcg3wAGiD - Sundeep
2个回答

2

关于你的代码的一些注意事项:

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

  2. 尝试匹配,如果匹配成功-使用匹配组格式化输出
    Python正则表达式提供了一个完美适合您情况的函数:re.subn()。 它执行正则表达式替换并返回所做的替换次数。

  3. 性能考虑

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

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

  4. 如何避免正则表达式预编译
    代码定义了匹配输入字符串时应遵循的正则表达式顺序。仅当我们百分之百确定需要它时才编译正则表达式。请参见下面的代码。比实际解释简单得多。
  5. 过早优化
    您没有遇到任何性能问题,是吗?通过这么早进行优化,您可能会花费一些时间而没有任何可观察的效果。

座右铭:

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
如果你绝对决定不要重复执行相同的正则表达式匹配,你可以这样做:
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)

个人认为这种微观优化的水平不会有太大的差别,但有一种方法可以完成。


嗨,Marius - 只是确认一下 - 你的意思是说我的代码没问题,而且你会做类似的事情,不必担心两次匹配?谢谢。 - Pavel Chernikov
是的,基本上是这样。不过你可能不应该使用 Exception,而是应该抛出更具体的异常,比如 ValueError - Marius

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