【问题标题】:How to parse a CSV with commas between parenthesis and missing values如何用括号和缺失值之间的逗号解析 CSV
【发布时间】:2017-11-01 13:03:59
【问题描述】:

我尝试使用 pyparsing 解析 CSV:

  • 括号(或方括号等)之间的逗号: "a(1,2),b" 应返回列表 ["a(1,2)","b"]
  • 缺失值:“a,b,,c”应返回列表 ['a','b','','c','']

我找到了一个解决方案,但它看起来很“脏”。主要是,Optional 里面只有一个可能的原子。我认为可选应该独立于原子。也就是说,我觉得它应该放在其他地方,例如在 delimitedList 可选参数中,但在我的反复试验中,这是唯一有效且有意义的地方。它可以在任何可能的原子中,所以我选择了第一个。

另外,我不完全理解 originalTextFor 在做什么,但如果我删除它,它就会停止工作。

工作示例:

import pyparsing as pp

# Function that parses a line of columns separated by commas and returns a list of the columns
def fromLineToRow(line):
    sqbrackets_col = pp.Word(pp.printables, excludeChars="[],") | pp.nestedExpr(opener="[",closer="]")  # matches "a[1,2]"
    parens_col = pp.Word(pp.printables, excludeChars="(),") | pp.nestedExpr(opener="(",closer=")")      # matches "a(1,2)"
    # In the following line:
    # * The "^" means "choose the longest option"
    # * The "pp.Optional" can be in any of the expressions separated by "^". I put it only on the first. It's used for when there are missing values
    atomic = pp.originalTextFor(pp.Optional(pp.OneOrMore(parens_col))) ^ pp.originalTextFor(pp.OneOrMore(sqbrackets_col))

    grammar = pp.delimitedList(atomic)

    row = grammar.parseString(line).asList()
    return row

file_str = \
"""YEAR,a(2,3),b[3,4]
1960,2.8,3
1961,4,
1962,,1
1963,1.27,3"""

for line in file_str.splitlines():
    row = fromLineToRow(line)
    print(row)

打印:

['YEAR', 'a(2,3)', 'b[3,4]']
['1960', '2.8', '3']
['1961', '4', '']
['1962', '', '1']
['1963', '1.27', '3']

这是正确的方法吗?有没有一种“更干净”的方式在第一个原子中使用 Optional

【问题讨论】:

    标签: python csv pyparsing


    【解决方案1】:

    从内到外,我明白了:

    # chars not in ()'s or []'s - also disallow ','
    non_grouped = pp.Word(pp.printables, excludeChars="[](),")
    
    # grouped expressions in ()'s or []'s
    grouped = pp.nestedExpr(opener="[",closer="]") | pp.nestedExpr(opener="(",closer=")")
    
    # use OneOrMore to allow non_grouped and grouped together
    atomic = pp.originalTextFor(pp.OneOrMore(non_grouped | grouped))
    # or based on your examples, you *could* tighten this up to:
    # atomic = pp.originalTextFor(non_grouped + pp.Optional(grouped))
    

    originalTextFor 在匹配表达式的前后边界内重新组合原始输入文本,并返回单个字符串。如果您忽略它,那么您将获得嵌套字符串列表中的所有子表达式,例如['a',['2,3']]。您可以通过重复调用''.join 重新加入它们,但这会折叠空白(或使用' '.join,但这会带来可能引入空白的相反问题)。

    要对列表的元素进行可选化,只要在分隔列表的定义中这样说:

    grammar = pp.delimitedList(pp.Optional(atomic, default=''))
    

    请务必添加默认值,否则空槽将被丢弃。

    通过这些更改,我得到:

    ['YEAR', 'a(2,3)', 'b[3,4]']
    ['1960', '2.8', '3']
    ['1961', '4', '']
    ['1962', '', '1']
    ['1963', '1.27', '3']
    

    【讨论】:

    • 对于数值的解析时转换,将atomic更改为:atomic = pp.pyparsing_common.number | pp.originalTextFor(... etc.
    【解决方案2】:

    你可以使用正则表达式re,例如:

    >>> import re
    >>> re.split(r',\s*(?![^()]*\))', line1)
    ['a(1,2)', 'b']
    >>> re.split(r',\s*(?![^()]*\))', line2)
    ['a', 'b', '', 'c', '']
    

    【讨论】:

    • line1 解析应该是 ["a(1,2)","b"] 而不是 ['a(1', '2)', 'b'] (括号内的逗号不应该是分隔符)
    • 是的,这是我在尝试 pyparsing 之前的第一种方法,但是当我开始添加方括号或任何其他类型的嵌套表达式时,正则表达式变得越来越难以辨认
    【解决方案3】:
    import re
    
    with open('44289614.csv') as f:
        for line in map(str.strip, f):
            l = re.split(',\s*(?![^()[]]*[\)\]])', line)
            print(len(l), l)
    

    输出:

    3 ['YEAR', 'a(2,3)', 'b[3,4]']
    3 ['1960', '2.8', '3']
    3 ['1961', '4', '']
    3 ['1962', '', '1']
    3 ['1963', '1.27', '3']
    

    修改自this answer

    我也喜欢this answer,它建议稍微修改输入并使用csv 模块的quotechar

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-15
      • 1970-01-01
      • 2010-11-29
      • 1970-01-01
      相关资源
      最近更新 更多