如何从一行文字创建字典?

Joh*_*web 8 python parsing dictionary

我有一个包含数千行的生成文件,如下所示:

CODE,XXX,DATE,20101201,TIME,070400,CONDITION_CODES,LTXT,PRICE,999.0000,QUANTITY,100,TSN,1510000001

某些行具有更多字段而其他行具有更少字段,但所有行都遵循相同的键值对模式,并且每行具有TSN字段.

在对文件进行一些分析时,我写了一个像下面这样的循环来将文件读入字典:

#!/usr/bin/env python

from sys import argv

records = {}
for line in open(argv[1]):
    fields = line.strip().split(',')
    record = dict(zip(fields[::2], fields[1::2]))
    records[record['TSN']] = record

print 'Found %d records in the file.' % len(records)
Run Code Online (Sandbox Code Playgroud)

...这很好,完全符合我的要求(这print只是一个简单的例子).

但是,对我来说它并没有感觉特别"pythonic"和以下行:

dict(zip(fields[::2], fields[1::2]))
Run Code Online (Sandbox Code Playgroud)

这只是感觉"笨重"(它在田野上迭代了多少次?).

是否有更好的方法在Python 2.6中使用标准模块进行此操作?

mar*_*eau 19

在Python 2中,您可以izipitertools模块中使用生成器对象的魔力来编写自己的函数,以简化dict记录的值对的创建.我pairwise()从Python 2 文档中的一个类似命名(但功能不同)的配方中得到了这个想法itertools.

要在Python 3中使用该方法,您可以使用plain,zip()因为它执行izip()Python 2中的操作,导致后者被删除itertools- 下面的示例解决了这个问题,并且应该在两个版本中都有效.

try:
    from itertools import izip
except ImportError:  # Python 3
    izip = zip

def pairwise(iterable):
    "s -> (s0,s1), (s2,s3), (s4, s5), ..."
    a = iter(iterable)
    return izip(a, a)
Run Code Online (Sandbox Code Playgroud)

在文件读取for循环中可以这样使用:

from sys import argv

records = {}
for line in open(argv[1]):
    fields = (field.strip() for field in line.split(','))  # generator expr
    record = dict(pairwise(fields))
    records[record['TSN']] = record

print('Found %d records in the file.' % len(records))
Run Code Online (Sandbox Code Playgroud)

但等等,还有更多!

可以创建一个我将调用的通用版本grouper(),它同样对应于一个类似命名但功能不同的itertools配方(在下面列出pairwise()):

def grouper(n, iterable):
    "s -> (s0,s1,...sn-1), (sn,sn+1,...s2n-1), (s2n,s2n+1,...s3n-1), ..."
    return izip(*[iter(iterable)]*n)
Run Code Online (Sandbox Code Playgroud)

在你的for循环中可以这样使用:

    record = dict(grouper(2, fields))
Run Code Online (Sandbox Code Playgroud)

当然,对于这样的特定情况,它很容易使用functools.partial()并创建一个类似的pairwise()功能(它将在Python 2和3中都有效):

import functools
pairwise = functools.partial(grouper, 2)
Run Code Online (Sandbox Code Playgroud)

后记

除非有非常多的字段,否则您可以在成对的行项目中创建一个实际的序列(而不是使用没有的生成器表达式len()):

fields = tuple(field.strip() for field in line.split(','))
Run Code Online (Sandbox Code Playgroud)

这样做的好处是可以使用简单的切片完成分组:

try:
    xrange
except NameError:  # Python 3
    xrange = range

def grouper(n, sequence):
    for i in xrange(0, len(sequence), n):
        yield sequence[i:i+n]

pairwise = functools.partial(grouper, 2)
Run Code Online (Sandbox Code Playgroud)

  • 非常感谢.提供的所有答案都非常出色,但是当您运行2.2 Gb文件(甚至比itertools版本更快)时,您的代码速度最快,并且易于阅读和单元测试.我正在努力不去思考itertools,那里有很多好东西. (2认同)
  • @Johnsyweb:关于表现的好消息.我为这一个感到自豪,并且已经很高兴最终确定了一个相当优雅的方式来做到这一点,因为这是我经常在我自己的日常Python代码中需要的东西. (2认同)

Ign*_*ams 6

没有那么好,只是效率更高......

完整的解释

  • 这里的技巧是使用列表乘法和`*args`"dereferencing"来确保将两个参数传递给`zip`的同一个对象,这样每次`zip`创建一个新输出时迭代器状态就会被共享并提前两次元组.我们可以通过其他几种方式来做到这一点:`x = iter(l); zip(x,x)`可能更具可读性; `(lambda x:zip(x,x))(iter(l))`对于功能编程人员来说可能更熟悉,虽然这种方式几乎是为了假装我们编程时没有副作用,实际上我们是完全依赖于一个;) (4认同)