在Python中解析行:使用正则表达式还是不使用?

13 投票
3 回答
2788 浏览
提问于 2025-04-17 12:37

我是一名 Perl 程序员,现在想通过把以前做过的工作转成 Python 来学习 Python。这不是逐行翻译。我想学习的是Python 的技巧,来完成这种任务。

我正在解析一个 Windows 的 INI 文件。部分名称的格式是:

[<type> <description>]

这里的 <type> 是一个单词,不区分大小写。而 <description> 可以是多个单词。

在每个部分后面,有一堆参数和对应的值。它们的格式是:

 <parameter> = <value>

参数中不能有空格,只能包含下划线、字母和数字(不区分大小写)。所以,第一个 = 是参数和对应值的分隔符。参数和值之间可能会有空格,也可能在行的开头或结尾有多余的空格。

在 Perl 中,我使用了正则表达式来解析:

while (my $line = <CONTROL_FILE>) {
    chomp($line);
    next if ($line =~ /^\s*[#;']/);     #Comments start with "#", ";", or "'"
    next if ($line =~ /^\s*$/);         #Ignore blank lines

    if ($line =~ /^\s*\[\s*(\w+)\s+(.*)/) {    #Section
        say "This is a '$1' section called '$2'";
    }
    elsif ($line =~ /^\s*(\w+)\s*=\s*(.*)/) {   #Parameter
       say "Parameter is '$1' with a value of '$2'";
    }
    else {      #Not Comment, Section, or Parameter
        say "Invalid line";
    }

}

问题是我被 Perl 的思维方式影响了,所以我觉得最简单的方法就是用正则表达式。以下是我目前的代码……

 for line in file_handle:
     line = line.strip

     # Comment lines and blank lines
     if line.find("#") == 1 \
             or line.find(";") == 1 \
             or line.whitespace:
         continue

    # Found a Section Heading
    if line.find("[") == 1:
        print "I want to use a regular expression here"
        print "to split the section up into two pieces"
    elif line.find("=") != -1:
        print "I want to use a regular expression here"
        print "to split the parameter into key and value"
    else
        print "Invalid Line"

这里有几个让我烦恼的地方:

  • 有两个地方似乎很适合用正则表达式。用 Python 来做这种分割的方式是什么?
  • 我确保在字符串的两边去掉空格,并重新写这个字符串。这样我就不需要多次去掉空格了。不过,我知道在 Python 中重新写字符串是个效率很低的操作。用 Python 处理这个问题的正确方法是什么?
  • 最后,我的算法看起来和我的 Perl 算法差不多,这让我觉得我在用 Perl 的思维方式来限制自己。我的 Python 代码应该怎么结构化?

我一直在看各种在线教程,它们帮助我理解了语法,但对如何处理语言本身,尤其是对一个习惯用另一种语言思考的人来说,帮助不大。

我的问题是:

  • 我应该使用正则表达式吗?还是有其他更好的方法来处理这个?
  • 我的编码逻辑正确吗?我应该如何思考来解析这个文件?

3 个回答

0

当然可以在这种情况下使用正则表达式。你要解析的.INI文件的行的语法,数学上来说符合一种叫做“乔姆斯基类型3”(也就是正则)的语法,这正是正则表达式的用途。

你需要的正则表达式大概是这样的(我随便想的,没测试过):

r"^\[\s*(\w)\s+(.*)\]$"

还有

r"^(\w)\s*\=\s*(.*)$"

使用 re.search,在返回的 匹配对象中,你可以提取出和表达式中括号部分对应的组。

5

Python有一个可以处理ini文件的库,叫做ini解析库。如果你想自己做一个库来解析ini文件,那你就需要一个真正的解析器。光靠正则表达式是不够的,建议使用PLY,或者用C语言的flex/bison工具。还有其他的Python解析资源可以参考。

词法分析器(lexer)会帮你处理所有的文本读取和树结构构建,因为这些工作比较机械,容易出错。比如说这部分:

while (my $line = <CONTROL_FILE>) {
    chomp($line);
    next if ($line =~ /^\s*[#;']/);     #Comments start with "#", ";", or "'"
    next if ($line =~ /^\s*$/);         #Ignore blank lines

    if ($line =~ /^\s*\[\s*(\w+)\s+(.*)/) {    #Section
        say "This is a '$1' section called '$2'";
    }
    elsif ($line =~ /^\s*(\w+)\s*=\s*(.*)/) {   #Parameter
       say "Parameter is '$1' with a value of '$2'";
    }
    else {      #Not Comment, Section, or Parameter
        say "Invalid line";
    }

}

就是由词法分析器生成的,你只需要定义正确的正则表达式。解析器会从词法分析器那里获取这些“标记”(tokens),并判断它们是否符合允许的标记模式。也就是说:

[<type> <description>]
<parameter> = <value>

你需要定义这些标记,以及它们如何组合在一起。其他的部分就会自动组合起来。对于那些觉得用简单的for循环和一些正则表达式就能做得更好的人,我建议你看看《Lex & Yacc, 第2版》

如果你想看看我用PLY写的一个示例解析器,可以点击这里。它解析的是一种叫做“jetLetter”的文件,这其实是groff/troff的一种方言。

5

虽然我觉得这不是你的本意,但这个文件格式看起来和Python自带的ConfigParser模块挺像的。有时候,最“Pythonic”的方法其实已经为你准备好了。(:

直接回答你的问题:正则表达式可能是个不错的选择。否则,你也可以试试更基础(但不太强大)的方式。

(parameter, value) = line.split('=')

如果这一行没有'='字符或者有多个'='字符,这段代码会报错。你可能想先用'=' in line来测试一下。

另外:

line.find("[") == 1

可能更好用下面这个来替代:

line.startswith("[")

希望这能帮到你一点(:

撰写回答