【问题标题】:How to create a dictionary from a line of text?如何从一行文本创建字典?
【发布时间】:2011-05-20 08:54:46
【问题描述】:

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

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)

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

但是,对我来说,它并没有特别“pythonic”的感觉:

dict(zip(fields[::2], fields[1::2]))

这只是感觉“笨拙”(它在字段上迭代了多少次?)。

有没有更好的方法在 Python 2.6 中只使用标准模块?

【问题讨论】:

  • 我认为这已经是 Pythonic 了。
  • 您只对 TSN 记录感兴趣吗?或者您打算将其扩展到所有记录类型?

标签: python parsing dictionary


【解决方案1】:

【讨论】:

  • 这里的诀窍是使用列表乘法和*args“解引用”来确保将相同的对象为两个参数传递给zip,这样迭代器状态每次被共享和推进两次zip 创建一个新的输出元组。我们可以通过其他几种方式做到这一点:x = iter(l); zip(x, x) 可能更具可读性; (lambda x: zip(x, x))(iter(l)) 对于函数式编程的人来说可能更熟悉,尽管这种方式几乎是为了假装我们正在编程而没有副作用,而实际上我们完全依赖于一个;)
  • @Karl Knechtel:可以使用(lambda x=iter(x): zip(x, x))() 代替(lambda x: zip(x, x))(iter(x)),尽管仍然依赖于[不同的] 副作用,但它的可读性可能略高。
【解决方案2】:
import itertools

def grouper(n, iterable, fillvalue=None):
    "grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return itertools.izip_longest(fillvalue=fillvalue, *args)

record = dict(grouper(2, line.strip().split(","))

source

【讨论】:

  • 不幸的是,我发现这只是recipes 文档中recipes 之一的逐字副本——或者我会拥有什么称抄袭,因为没有给出参考或引用。
  • @martineau:他的下方确实有一个标记为“来源”的小链接。
  • @Ignacio Vazquez-Abrams:哦……显然我错过了——抱歉@robert——但仍然认为它不值得投票。
【解决方案3】:

在 Python 2 中,您可以使用 itertools 模块中的 izip 和生成器对象的魔力来编写您自己的函数,以简化为 dict 记录创建值对。我从 Python 2 itertools 文档中的名称相似(尽管功能不同)recipe 得到了 pairwise() 的想法。

要在 Python 3 中使用该方法,您可以只使用普通的 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)

在你的文件中可以像这样使用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))

但是等等,还有更多!

可以创建一个通用版本,我将其称为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)

for 循环中可以这样使用:

    record = dict(grouper(2, fields))

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

import functools
pairwise = functools.partial(grouper, 2)

后记

除非有大量的字段,否则您可以从成对的行项目中创建一个实际的序列(而不是使用没有len()generator expression):

fields = tuple(field.strip() for field in line.split(','))

优点是它允许使用简单的切片来完成分组:

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)

【讨论】:

  • 非常感谢。提供的所有答案都非常好,但您的代码在 2.2 Gb 文件上运行时速度最快(甚至比 itertools 版本更快)并且易于阅读和单元测试。我因为没有考虑看 itertools 而自责,里面有很多好东西。
  • @Johnsyweb:关于性能的好消息。我为此感到自豪,并且已经很高兴终于确定了一种相当优雅的方式来做到这一点,因为这是我在自己的日常 Python 代码中经常发现的需要。
【解决方案4】:

如果我们无论如何都要把它抽象成一个函数,那么“从头开始”编写并不难:

def pairs(iterable):
    iterator = iter(iterable)
    while True:
        try: yield (iterator.next(), iterator.next())
        except: return

不过,robert 的食谱版本肯定会因为灵活性而获得加分。

【讨论】:

  • FWIW,这不是“罗伯特的食谱”,请参阅我在他的 answer 下的评论。
猜你喜欢
  • 1970-01-01
  • 2016-03-09
  • 1970-01-01
  • 2012-04-10
  • 2020-03-14
  • 1970-01-01
  • 2012-07-16
  • 1970-01-01
相关资源
最近更新 更多