Python 如何使用csv嗅探器检测双字符/连续字符作为分隔符?

Python 如何使用csv嗅探器检测双字符/连续字符作为分隔符?,python,csv,whitespace,separator,sniffer,Python,Csv,Whitespace,Separator,Sniffer,例如,下表使用双空格作为表值的分隔符,但在标题行中的开口圆括号之前使用单空格。当csv.Sniffer应用于文件时,将返回单个空格作为分隔符,而不是预期的双空格。如何获得双空格作为结果分隔符 日ddd N d E d X m Y Z c S p 277 40.52 -36.59 0.11 -0.50 -1.62 30.17 277 40.52 -36.49 0.18 -0.04 -1.66 30.14 277 40.51 -36.39 0.14 -0.07 -1

例如,下表使用双空格作为表值的分隔符,但在标题行中的开口圆括号之前使用单空格。当csv.Sniffer应用于文件时,将返回单个空格作为分隔符,而不是预期的双空格。如何获得双空格作为结果分隔符

日ddd N d E d X m Y Z c S p 277 40.52 -36.59 0.11 -0.50 -1.62 30.17 277 40.52 -36.49 0.18 -0.04 -1.66 30.14 277 40.51 -36.39 0.14 -0.07 -1.64 30.15
这本质上是欺骗-如果您认为可能以“”作为分隔符,请重写它们:

with open("example.csv","w") as f:
    f.write("""Day (ddd)  N (d)  E (d)  X (m)  Y(s)  Z (c)  S (p)
277  40.52  -36.59  0.11  -0.50  -1.62  30.17
277  40.52  -36.49  0.18  -0.04  -1.66  30.14
277  40.51  -36.39  0.14  -0.07  -1.64  30.15""")

import csv
import io
import re


# keeps the whole file in memory - for big files >2G you'll need to rewrite
# to linebased parsing
stringIO = io.StringIO()
with open('example.csv') as csvfile:
    for line in csvfile:
        stringIO.write(re.sub("  ","|",line))

stringIO.seek(0)
sn = csv.Sniffer()
dialect = sn.sniff(stringIO.read(1024))

# back to start
stringIO.seek(0)
reader = csv.reader(stringIO, dialect)
for line in reader:
    print(line)
输出:

['Day (ddd)', 'N (d)', 'E (d)', 'X (m)', 'Y(s)', 'Z (c)', 'S (p)']
['277', '40.52', '-36.59', '0.11', '-0.50', '-1.62', '30.17']
['277', '40.52', '-36.49', '0.18', '-0.04', '-1.66', '30.14']
['277', '40.51', '-36.39', '0.14', '-0.07', '-1.64', '30.15']

如果文件不包含嗅探器,则嗅探器可用于普通分隔符,如果包含嗅探器,则会将其替换为“|”,希望您的文件中尚未使用“|”,您可能需要检查它并使用“;,”等。如果使用,则改为使用。

这本质上是欺骗-如果您认为可能使用“”作为分隔符,请重写它们:

with open("example.csv","w") as f:
    f.write("""Day (ddd)  N (d)  E (d)  X (m)  Y(s)  Z (c)  S (p)
277  40.52  -36.59  0.11  -0.50  -1.62  30.17
277  40.52  -36.49  0.18  -0.04  -1.66  30.14
277  40.51  -36.39  0.14  -0.07  -1.64  30.15""")

import csv
import io
import re


# keeps the whole file in memory - for big files >2G you'll need to rewrite
# to linebased parsing
stringIO = io.StringIO()
with open('example.csv') as csvfile:
    for line in csvfile:
        stringIO.write(re.sub("  ","|",line))

stringIO.seek(0)
sn = csv.Sniffer()
dialect = sn.sniff(stringIO.read(1024))

# back to start
stringIO.seek(0)
reader = csv.reader(stringIO, dialect)
for line in reader:
    print(line)
输出:

['Day (ddd)', 'N (d)', 'E (d)', 'X (m)', 'Y(s)', 'Z (c)', 'S (p)']
['277', '40.52', '-36.59', '0.11', '-0.50', '-1.62', '30.17']
['277', '40.52', '-36.49', '0.18', '-0.04', '-1.66', '30.14']
['277', '40.51', '-36.39', '0.14', '-0.07', '-1.64', '30.15']

如果文件不包含嗅探器,则嗅探器可用于普通分隔符,如果包含嗅探器,则会将其替换为“|”,希望您的文件中尚未使用“|”,您可能需要检查它并使用“;,”如我在评论中所说,csv模块通常不支持多字符分隔符。但是,在这种特殊情况下,有一种解决方法,因为分隔符恰好是一个空白字符

import csv

filename = "double_whitespace.csv"

with open(filename, 'r', newline='') as file:
    reader = csv.reader(file, delimiter=' ', skipinitialspace=True)
    next(reader)  # Skip header row.
    for row in reader:
        print(row)
输出:

['Day (ddd)', 'N (d)', 'E (d)', 'X (m)', 'Y(s)', 'Z (c)', 'S (p)']
['277', '40.52', '-36.59', '0.11', '-0.50', '-1.62', '30.17']
['277', '40.52', '-36.49', '0.18', '-0.04', '-1.66', '30.14']
['277', '40.51', '-36.39', '0.14', '-0.07', '-1.64', '30.15']
['277', '40.52', '-36.59', '0.11', '-0.50', '-1.62', '30.17'] ['277', '40.52', '-36.49', '0.18', '-0.04', '-1.66', '30.14'] ['277', '40.51', '-36.39', '0.14', '-0.07', '-1.64', '30.15']
正如我在评论中所说,csv模块通常不支持多字符分隔符。但是,在这种特殊情况下,有一种解决方法,因为分隔符恰好是一个空白字符

import csv

filename = "double_whitespace.csv"

with open(filename, 'r', newline='') as file:
    reader = csv.reader(file, delimiter=' ', skipinitialspace=True)
    next(reader)  # Skip header row.
    for row in reader:
        print(row)
输出:

['Day (ddd)', 'N (d)', 'E (d)', 'X (m)', 'Y(s)', 'Z (c)', 'S (p)']
['277', '40.52', '-36.59', '0.11', '-0.50', '-1.62', '30.17']
['277', '40.52', '-36.49', '0.18', '-0.04', '-1.66', '30.14']
['277', '40.51', '-36.39', '0.14', '-0.07', '-1.64', '30.15']
['277', '40.52', '-36.59', '0.11', '-0.50', '-1.62', '30.17'] ['277', '40.52', '-36.49', '0.18', '-0.04', '-1.66', '30.14'] ['277', '40.51', '-36.39', '0.14', '-0.07', '-1.64', '30.15']
下面是另一个解决方案,它使用了这样一个事实:传递给它的第一个参数必须是类似文件的。这意味着您可以提供自定义类的实例,该实例将文件行中的双空格字符更改为其他单个字符,如逗号。如果这样做,则可以像处理其他行一样处理标题行

以下是我的建议:

import csv

class CSV_Translater:
    """ File-like object that translates characters. """
    def __init__(self, f, old, new):
        self.f, self.old, self.new = f, old, new
    def __iter__(self):
        return self
    def __next__(self):
        return next(self.f).replace(self.old, self.new)


if __name__ == '__main__':

    DELIMITER = ','
    filename = "double_whitespace.csv"

    with open(filename, 'r', newline='') as file:
        translator = CSV_Translater(file, '  ', DELIMITER)
        for row in csv.reader(translator, delimiter=DELIMITER):
            print(row)
输出

['daydddd','ndd','eded','xm','Ys','zc','sps'] ['277', '40.52', '-36.59', '0.11', '-0.50', '-1.62', '30.17'] ['277', '40.52', '-36.49', '0.18', '-0.04', '-1.66', '30.14'] ['277', '40.51', '-36.39', '0.14', '-0.07', '-1.64', '30.15']
下面是另一个解决方案,它使用了这样一个事实:传递给它的第一个参数必须是类似文件的。这意味着您可以提供自定义类的实例,该实例将文件行中的双空格字符更改为其他单个字符,如逗号。如果这样做,则可以像处理其他行一样处理标题行

以下是我的建议:

import csv

class CSV_Translater:
    """ File-like object that translates characters. """
    def __init__(self, f, old, new):
        self.f, self.old, self.new = f, old, new
    def __iter__(self):
        return self
    def __next__(self):
        return next(self.f).replace(self.old, self.new)


if __name__ == '__main__':

    DELIMITER = ','
    filename = "double_whitespace.csv"

    with open(filename, 'r', newline='') as file:
        translator = CSV_Translater(file, '  ', DELIMITER)
        for row in csv.reader(translator, delimiter=DELIMITER):
            print(row)
输出

['daydddd','ndd','eded','xm','Ys','zc','sps'] ['277', '40.52', '-36.59', '0.11', '-0.50', '-1.62', '30.17'] ['277', '40.52', '-36.49', '0.18', '-0.04', '-1.66', '30.14'] ['277', '40.51', '-36.39', '0.14', '-0.07', '-1.64', '30.15']
你真的需要闻一下吗?怀疑它的可能性…这个问题很容易可视化,尽管可能没有在csv.Sniffer中实现。不一定使用嗅探器,如何将双空格作为分隔符而不是单个空格?csv模块不支持多字符分隔符。谢谢Patrick Artner和martineau。对于获取正确分隔符的问题,还有其他解决方案吗?如果您确实知道它是这种格式,并且可以将文件保存在内存中,则可以将data=re.subr,|,file.read将其存储在StringIO中,并使用csv.readeriostring,delimiter=|来处理它。您真的需要嗅探它吗?怀疑它的可能性…这个问题很容易可视化,尽管可能没有在csv.Sniffer中实现。不一定使用嗅探器,如何将双空格作为分隔符而不是单个空格?csv模块不支持多字符分隔符。谢谢Patrick Artner和martineau。对于获取正确分隔符的问题,还有其他解决方案吗?如果您确实知道它是这种格式,并且可以将文件保存在内存中,则可以将data=re.subr,|,file.read将其存储在StringIO中,并使用csv.readeriostring,delimiter=|来处理它。@PatrickArtner:True,但是它可以被处理,并且只有一个标题…也许通过在那一行上执行re.sub或更简单的str.replace。谢谢。标题行应该保留,否则,只有一个分隔符。布伦纳:请看我刚才发布的另一个答案。@PatrickArtner:是的,但它可以是b
e处理,并且只有一个标题…可能通过在这一行上执行re.sub或更简单的str.replace。谢谢。标题行应该保留,否则,只有一个分隔符。布伦纳:请看我刚才发布的另一个答案。谢谢。在记忆中作弊的解决方案是有希望的。我得试试。在此之前,在替换它之前,还应该可以检测双空格作为分隔符。谢谢。在记忆中作弊的解决方案是有希望的。我得试试。在此之前,在替换它之前,还可以检测双空格作为分隔符。谢谢martineau。将文件封装到对象中的想法可能会让我更好地控制它。我仍然需要首先检测双空格,但是检测分隔符的问题似乎已经解决了。你必须自己检测它-因为正如我所说,csv模块不支持多字符分隔符,所以csv.Sniffer永远不会检测到它们,因为它们在技术上不是csv文件。您可以查看,也许可以创建一个处理它们的自定义版本。谢谢martineau。将文件封装到对象中的想法可能会让我更好地控制它。我仍然需要首先检测双空格,但是检测分隔符的问题似乎已经解决了。你必须自己检测它-因为正如我所说,csv模块不支持多字符分隔符,所以csv.Sniffer永远不会检测到它们,因为它们在技术上不是csv文件。您可以查看,或许可以创建一个自定义版本来处理它们。