Python 正则表达式读取类似CSV的行
我想处理一些类似CSV格式的数据行。数据值之间用逗号分隔(而且逗号周围可能有空格),值可以用单引号或双引号括起来。例如,下面这一行是有效的:
data1, data2 ,"data3'''", 'data4""',,,data5,
但这一行是格式不正确的:
data1, data2, da"ta3", 'data4',
-- 引号只能在前面或后面有空格。
这样的格式不正确的行应该被识别出来,最好能在行中标记出不正确的值,但如果正则表达式没有匹配整行也是可以接受的。
我正在尝试写一个正则表达式来解析这些数据,使用match()或findall(),但我想到的每一个正则表达式在处理一些特殊情况时都有问题。
所以,也许有经验的人能帮我解决这个问题?(或者说这对正则表达式来说太复杂了,我应该直接写一个函数)
编辑1:
csv
模块在这里用处不大:
>>> list(csv.reader(StringIO('''2, "dat,a1", 'dat,a2',''')))
[['2', ' "dat', 'a1"', " 'dat", "a2'", '']]
>>> list(csv.reader(StringIO('''2,"dat,a1",'dat,a2',''')))
[['2', 'dat,a1', "'dat", "a2'", '']]
-- 除非可以调整一下?
编辑2:做了一些语言上的修改 - 希望现在的英语更准确了
编辑3:感谢大家的回答,我现在很确定正则表达式在这里不是个好主意,因为(1)处理所有特殊情况可能很棘手(2)输出的格式不规律。写完这些,我决定去看看提到的pyparsing,或者自己写一个类似有限状态机的解析器。
6 个回答
4
Python有一个标准库模块可以用来读取csv文件:
import csv
reader = csv.reader(open('file.csv'))
for line in reader:
print line
对于你提供的示例输入,这段代码会输出:
['data1', ' data2 ', "data3'''", ' \'data4""\'', '', '', 'data5', '']
编辑:
你需要添加skipinitalspace=True,这样在双引号前面允许有空格,适用于你提供的额外示例。至于单引号的情况我还不太确定。
>>> list(csv.reader(StringIO('''2, "dat,a1", 'dat,a2','''), skipinitialspace=True))
[['2', 'dat,a1', "'dat", "a2'", '']]
>>> list(csv.reader(StringIO('''2,"dat,a1",'dat,a2','''), skipinitialspace=True))
[['2', 'dat,a1', "'dat", "a2'", '']]
12
虽然使用 csv
模块是解决这个问题的正确方法,但其实用正则表达式也能做到这一点:
import re
r = re.compile(r'''
\s* # Any whitespace.
( # Start capturing here.
[^,"']+? # Either a series of non-comma non-quote characters.
| # OR
"(?: # A double-quote followed by a string of characters...
[^"\\]|\\. # That are either non-quotes or escaped...
)* # ...repeated any number of times.
" # Followed by a closing double-quote.
| # OR
'(?:[^'\\]|\\.)*'# Same as above, for single quotes.
) # Done capturing.
\s* # Allow arbitrary space before the comma.
(?:,|$) # Followed by a comma or the end of a string.
''', re.VERBOSE)
line = r"""data1, data2 ,"data3'''", 'data4""',,,data5,"""
print r.findall(line)
# That prints: ['data1', 'data2', '"data3\'\'\'"', '\'data4""\'', 'data5']
编辑:如果你想验证行内容,可以在上面的正则表达式基础上稍微加一些内容:
import re
r_validation = re.compile(r'''
^(?: # Capture from the start.
# Below is the same regex as above, but condensed.
# One tiny modification is that it allows empty values
# The first plus is replaced by an asterisk.
\s*([^,"']*?|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')\s*(?:,|$)
)*$ # And don't stop until the end.
''', re.VERBOSE)
line1 = r"""data1, data2 ,"data3'''", 'data4""',,,data5,"""
line2 = r"""data1, data2, da"ta3", 'data4',"""
if r_validation.match(line1):
print 'Line 1 is valid.'
else:
print 'Line 1 is INvalid.'
if r_validation.match(line2):
print 'Line 2 is valid.'
else:
print 'Line 2 is INvalid.'
# Prints:
# Line 1 is valid.
# Line 2 is INvalid.