【问题标题】:CSV reader behavior with None and empty string无和空字符串的 CSV 阅读器行为
【发布时间】:2012-07-07 22:07:18
【问题描述】:

在使用 Python 的 csv 模块在 Python 数据结构和 csv 表示之间来回切换时,我想区分 None 和空字符串 ('')。

我的问题是当我跑步时:

import csv, cStringIO

data = [['NULL/None value',None],
        ['empty string','']]

f = cStringIO.StringIO()
csv.writer(f).writerows(data)

f = cStringIO.StringIO(f.getvalue())
data2 = [e for e in csv.reader(f)]

print "input : ", data
print "output: ", data2

我得到以下输出:

input :  [['NULL/None value', None], ['empty string', '']]
output:  [['NULL/None value', ''], ['empty string', '']]

当然,我可以使用datadata2 来区分None 和空字符串,例如:

data = [d if d!=None else 'None' for d in data]
data2 = [d if d!='None' else None for d in data2]

但这会部分削弱我对 csv 模块的兴趣(用 C 实现的快速反序列化/序列化,特别是在处理大型列表时)。

是否有csv.Dialectcsv.writercsv.reader 的参数可以让他们在这个用例中区分''None

如果没有,是否有兴趣对csv.writer 实施补丁以启用这种来回操作? (可能是 Dialect.None_translate_to 参数默认为 '' 以确保向后兼容。)

【问题讨论】:

    标签: python csv string nonetype


    【解决方案1】:

    您至少可以通过创建您自己版本的单例 None 类/值来回避 csv 模块所做的事情:

    from __future__ import print_function
    import csv
    
    
    class NONE(object):
        ''' None-like class. '''
        def __repr__(self): # Method csv.writer class uses to write values.
            return 'NONE'   # Unique string value to represent None.
        def __len__(self):  # Method called to determine length and truthiness.
            return 0
    
    NONE = NONE()  # Singleton instance of the class.
    
    
    if __name__ == '__main__':
    
        try:
            from cStringIO import StringIO  # Python 2.
        except ModuleNotFoundError:
            from io import StringIO  # Python 3.
    
        data = [['None value', None], ['NONE value', NONE], ['empty string', '']]
        f = StringIO()
        csv.writer(f).writerows(data)
    
        f = StringIO(f.getvalue())
        print(" input:", data)
        print("output:", [e for e in csv.reader(f)])
    

    结果:

     input: [['None value', None], ['NONE value', NONE],   ['empty string', '']]
    output: [['None value', ''],   ['NONE value', 'NONE'], ['empty string', '']]
    

    使用NONE 而不是None 将保留足够的信息,以便您能够区分它和任何实际的空字符串数据值。

    更好的选择……

    您可以使用相同的方法来实现一对相对轻量级的csv.readercsv.writer“代理”类——这是必要的,因为您实际上不能子类化用C 编写的内置csv 类——不会引入大量开销(因为大部分处理仍将由底层内置执行)。这将使发生的事情完全透明,因为它全部封装在代理中。

    from __future__ import print_function
    import csv
    
    
    class csvProxyBase(object): _NONE = '<None>'  # Unique value representing None.
    
    
    class csvWriter(csvProxyBase):
        def __init__(self, csvfile, *args, **kwrags):
            self.writer = csv.writer(csvfile, *args, **kwrags)
        def writerow(self, row):
            self.writer.writerow([self._NONE if val is None else val for val in row])
        def writerows(self, rows):
            list(map(self.writerow, rows))
    
    
    class csvReader(csvProxyBase):
        def __init__(self, csvfile, *args, **kwrags):
            self.reader = csv.reader(csvfile, *args, **kwrags)
        def __iter__(self):
            return self
        def __next__(self):
            return [None if val == self._NONE else val for val in next(self.reader)]
        next = __next__  # Python2.x compatibility.
    
    
    if __name__ == '__main__':
    
        try:
            from cStringIO import StringIO  # Python 2.
        except ModuleNotFoundError:
            from io import StringIO  # Python 3.
    
        data = [['None value', None], ['empty string', '']]
        f = StringIO()
        csvWriter(f).writerows(data)
    
        f = StringIO(f.getvalue())
        print("input : ", data)
        print("ouput : ", [e for e in csvReader(f)])
    

    结果:

     input: [['None value', None], ['empty string', '']]
    output: [['None value', None], ['empty string', '']]
    

    【讨论】:

    • 第一个解决方案的变体为我解决了写入问题。使用返回空字符串的 repr 创建了一个类 NONE(int)。用 NONE 替换了所有 None 值(无论如何我都必须格式化我的数据,所以没有额外的工作)。然后使用 QUOTE_NONNUMERIC 创建 csv 写入器。这有点 hacky,但这意味着在输出文件中,您知道带引号的字段始终是字符串,而未引用的空字段始终是 None。
    • @trelltron:很聪明,但一个潜在的缺点是它需要使用您可能不想要的QUOTE_NONNUMERIC,并且有可能使文件更大.我的解决方案不需要使用任何特定的 csv 选项即可工作。另请注意,我的第二种选择不需要用任何东西替换所有 None 值。
    【解决方案2】:

    The documentation 暗示你想要的东西是不可能的:

    为了尽可能轻松地与实现 DB API 的模块进行交互,值 None 被写为空字符串。

    这在writer 类的文档中,表明它适用于所有方言,并且是 csv 模块的固有限制。

    我支持改变这一点(以及 csv 模块的各种其他限制),但人们可能希望将此类工作卸载到不同的库中,并保持 CSV 模块简单(或在至少尽可能简单)。

    如果您需要更强大的文件读取功能,您可能希望查看 numpy、scipy 和 pandas 中的 CSV 读取功能,我记得它们有更多选择。

    【讨论】:

    • 是的,已确认:查看 Modules/_csv.c 中的 csv_writerow ( if (field == Py_None) ... )。没有办法区分 '' 和 None。真可惜,考虑到方言抽象,您本来希望有更多的灵活性。您提到了 csv 模块的其他限制,您介意详细说明吗(如果还有其他问题我真的应该开始查看其他 csv-reading 写作)?
    • 我发现有时令人讨厌的一个限制是分隔符必须是单个字符。因此,您无法解析列由两个选项卡分隔的文件。就像您遇到的 None 一样,这很容易解决,但仍然很烦人。
    • 另一个是模块内的硬编码ascii限制。
    【解决方案3】:

    我认为仅仅用方言是不可能做你想做的事的,但你可以编写自己的 csv.reader/write 子类。另一方面,我仍然认为这对于这个用例来说太过分了。即使您想捕获的不仅仅是None,您也可能只想要str()

    >>> data = [['NULL/None value',None],['empty string','']]
    >>> i = cStringIO.StringIO()
    >>> csv.writer(i).writerows(map(str,row) for row in data)
    >>> print i.getvalue()
    NULL/None value,None
    empty string,
    

    【讨论】:

      【解决方案4】:

      由于您可以控制序列化数据的使用者和创建者,因此请考虑使用支持这种区别的格式。

      例子:

      >>> import json
      >>> json.dumps(['foo', '', None, 666])
      '["foo", "", null, 666]'
      >>>
      

      【讨论】:

        【解决方案5】:

        正如其他人指出的那样,您不能通过csv.Dialectcsv.writer 和/或csv.reader 的参数真正做到这一点。然而,正如我在一条评论中所说,您可以通过有效继承后两者来实现它(您显然不能真正做到,因为它们是内置的)。 “子类”在写入时所做的只是拦截 None 值并将它们更改为唯一的字符串,并在读回它们时反转过程。这是一个完整的示例:

        import csv, cStringIO
        NULL = '<NULL>'  # something unlikely to ever appear as a regular value in your csv files
        
        class MyCsvWriter(object):
            def __init__(self, *args, **kwrds):
                self.csv_writer = csv.writer(*args, **kwrds)
        
            def __getattr__(self, name):
                return getattr(self.csv_writer, name)
        
            def writerow(self, row):
                self.csv_writer.writerow([item if item is not None else NULL
                                              for item in row])
            def writerows(self, rows):
                for row in rows:
                    self.writerow(row)
        
        class MyCsvReader(object):
            def __init__(self, *args, **kwrds):
                self.csv_reader = csv.reader(*args, **kwrds)
        
            def __getattr__(self, name):
                return getattr(self.csv_reader, name)
        
            def __iter__(self):
                rows = iter(self.csv_reader)
                for row in rows:
                    yield [item if item != NULL else None for item in row]
        
        data = [['NULL/None value', None],
                ['empty string', '']]
        
        f = cStringIO.StringIO()
        MyCsvWriter(f).writerows(data)  # instead of csv.writer(f).writerows(data)
        
        f = cStringIO.StringIO(f.getvalue())
        data2 = [e for e in MyCsvReader(f)]  # instead of [e for e in csv.reader(f)]
        
        print "input : ", data
        print "ouput : ", data2
        

        输出:

        input :  [['NULL/None value', None], ['empty string', '']]
        ouput :  [['NULL/None value', None], ['empty string', '']]
        

        这有点冗长,可能会稍微减慢 csv 文件的读取和写入速度(因为它们是用 C/C++ 编写的),但这可能没什么区别,因为无论如何该进程都可能是低级 I/O 绑定的。

        【讨论】:

          【解决方案6】:

          我也遇到这个问题,找到这个https://bugs.python.org/issue23041

          问题的解决方案:

          • 子类 csv.DictWriter,使用字典作为元素类型,并让其 writerow 方法执行特定于应用程序的工作。
          • 定义一个 writerow() 函数,该函数执行类似的操作(本质上是包装 csv.writerow())。

          【讨论】:

            【解决方案7】:

            如上所述,这是csv 模块的限制。一个解决方案就是用简单的字典理解重写循环内的行,如下所示:

            reader = csv.DictReader(csvfile)
            for row in reader:
                # Interpret empty values as None (instead of '')
                row = {k: v if v else None for k, v in row.items()}
                :
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2018-02-06
              • 2015-04-11
              • 1970-01-01
              • 1970-01-01
              • 2015-10-09
              • 1970-01-01
              相关资源
              最近更新 更多