【问题标题】:Parsing Lines in Python: Use RE or Not?在 Python 中解析行:使用 RE 还是不使用?
【发布时间】:2012-03-01 09:13:15
【问题描述】:

我是一名 Perl 程序员,正在尝试通过完成我之前完成的一些工作并将其转换为 Python 来学习 Python。这不是逐行翻译。我想学习 Python 技术 来完成这类任务。

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

[<type> <description>]

&lt;type&gt; 是一个单词字段,不区分大小写。 &lt;description&gt; 可以是多个单词。

在一段之后,有一堆参数和值。它们的形式为:

 <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 中构建?

我一直在阅读各种在线教程,它们帮助我理解了语法,但在处理语言本身的方式上并没有太多帮助——尤其是那些倾向于用另一种语言思考的人。

我的问题:

  • 我应该使用正则表达式吗?或者,有没有其他更好的方法来处理这个问题?
  • 我的编码逻辑是否正确?我应该如何考虑解析这个文件?

【问题讨论】:

  • 请务必查看ConfigParser 模块。
  • 在 Perl 中你不是 corrupt,你是 bless
  • @SvenMarnach - 感谢您的建议,但我已经看到了。问题是 ConfigParser 将输出放入字典中,我无法保证字典中部分的顺序,这在这个特定的应用程序中非常重要。我在使用 Config::Ini 模块时遇到了与 Perl 相同的问题。此外,这让我有机会真正了解 Python 的来龙去脉。
  • 从 Python 2.6 开始,您可以传入与 dict 不同的类型,并使用提供有序字典的库之一。从 Python 2.7 开始,OrderedDict 包含在标准库和 ConfigParser 的标准字典类型中。

标签: python for-loop


【解决方案1】:

是的,在这种情况下一定要使用正则表达式。您尝试解析的 .INI 文件行的语法在数学上符合 Chomsky Type 3(常规)语法的特征,这正是正则表达式旨在解析的那种东西。

您需要的正则表达式(在我的脑海中,未经测试)类似于:

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

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

使用re.search,在返回的Match objects中,可以提取表达式中带括号的分组对应的分组。

【讨论】:

    【解决方案2】:

    Python 包含一个ini parsing library。如果您想构建一个库来解析 ini 文件,那么您正在查看 actual 解析器。正则表达式不会削减它,使用 PLY 或挂钩在 flex/bison C 解析器中。 Additional python parsing resources are available as well.

    词法分析器会为您处理所有文本消耗和树构造,因为这是一项容易出现程序员错误的机械任务。 IE。本节:

    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";
        }
    
    }
    

    由词法分析器创建,您只需要定义正确的正则表达式。解析器从词法分析器中提取标记,并确定它们是否符合允许的标记模式。那就是:

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

    定义这些标记,然后定义它们如何适合。其他一切都只是把自己放在一起。对于那些认为你可以通过快速 for 循环和一些正则表达式做得更好的人,我建议你阅读Lex & Yacc, 2nd Ed.

    对于我使用 PLY 编写的解析器示例,go here。它解析一个“jetLetter”文件,它只是groff/troff的方言。

    【讨论】:

    • +1 展示了“Python”做很多事情的方式通常如何知道强大的库构建。
    • 只是想提供一个指向 lepl 的链接,这是我最近在这个网站上了解到的一个不错的轻量级解析库。
    【解决方案3】:

    虽然我不认为这是您的意图,但文件格式看起来与 Python 的内置 ConfigParser 模块非常相似。有时已经为您提供了最“Pythonic”的方式。 (:

    更直接地回答您的问题:正则表达式可能是执行此操作的好方法。否则,您可以尝试更基本的(和不太健壮的)

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

    如果该行不包含或包含多个“=”字符,这将引发错误。您可能需要先使用'=' in line 对其进行测试。

    还有:

    line.find("[") == 1
    

    最好用

    代替
    line.startswith("[")
    

    希望能有所帮助(:

    【讨论】:

    • 谢谢,我确实看到了那个模块,但不幸的是,它将结果存储在字典中,您可能会丢失部分的读取顺序。对我来说,部分的顺序非常重要.我在 Perl 中使用 Config::Ini 模块时遇到了同样的问题。此外,这个想法是学习语言。感谢您提供指向 startswith 方法的指针。
    • @David 不客气。我认为内置的方式不会完全一样,不知何故...... :)
    • 要避免超过 1 个“=”符号,请使用 line.split('=',1) 要同时解决没有“=”符号的问题,请使用 parameter,value = (line.split('=',1)+[''])[:2]。不要将 () 放在 LHS 元组周围,它们是不必要的混乱。还要确保使用line.strip() 调用line.strip - 您拥有的代码将用绑定的方法条替换行,我确定这是不希望的。
    • 而且 str 没有 whitespace 方法。测试和忽略空行的最简单方法是line = line.strip(),然后是if not line: continue
    • @PaulMcGuire - 我写的代码几乎是按照发布的方式写的。我只是想确保我在这方面朝着正确的方向前进。我发现whitespace 不是方法,发现split 不带参数。我需要一段时间才能掌握 Python 文档的窍门。我已经很清楚你所说的了,除了我的 LHS 元组周围有括号。我会删除它们。我需要一段时间来学习 Python。例如,如何确定变量是否已定义。我寻找了defined 命令,但不存在。意识到你做了一个try/except。谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-22
    • 1970-01-01
    • 1970-01-01
    • 2011-09-23
    • 2015-04-01
    相关资源
    最近更新 更多