2010-12-04 25 views
8

tengo un archivo generado con miles de líneas como la siguiente:¿Cómo crear un diccionario a partir de una línea de texto?

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

Algunas líneas tienen más campos y otros tienen menos, pero todos siguen el mismo patrón de pares de valores clave y cada línea tiene una Campo TSN

Al hacer un análisis sobre el archivo, escribí un bucle como el siguiente para leer el archivo en un diccionario:

#!/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) 

... que está muy bien y hace exactamente lo que yo quiero que (la print es solo un ejemplo trivial).

Sin embargo, no se siente particularmente "Pythonic" para mí y la línea con: (¿cuántas veces no es iterar sobre los campos)

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

cual sólo se siente "torpe".

¿Hay una forma mejor de hacerlo en Python 2.6 con solo los módulos estándar a mano?

+0

Creo que esto es tan pitónico como puede ser. –

+0

¿Solo le interesan los registros de TSN? ¿O tiene la intención de expandir esto a todos los tipos de registros? – marcog

Respuesta

18

En Python 2 podría utilizar izip en el módulo itertools y la magia del generador de objetos para escribir su propia función para simplificar la creación de pares de valores para los registros dict. Obtuve la idea de pairwise() de un nombre similar (pero funcionalmente diferente) recipe en Python 2 itertools documentos.

Para utilizar el enfoque en Python 3, sólo puede utilizar llanura zip() ya que hace lo hicieron izip() en Python 2 resulta en la eliminación de este último de itertools — el siguiente ejemplo direcciones este y debería funcionar en ambas versiones.

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) 

que puede ser utilizado como esta en el archivo de lectura for bucle:

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)) 

Pero espere, hay más!

Es posible crear una versión generalizada que llamaré grouper(), que corresponde de nuevo a un nombre similar, pero funcionalmente diferentes itertools receta (que aparece justo debajo de 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) 

que podría ser utilizado como este en su for bucle:

record = dict(grouper(2, fields)) 

por supuesto, para casos específicos como este, es fácil de usar functools.partial() y crear un pairwise() función similar con ella (que funcionará tanto en Python 2 & 3):

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

PostScript

A menos que haya un muy gran número de campos, en su lugar podría crear una secuencia real de los pares de elementos de línea (en lugar de utilizar un generador de expresión que no tiene len()):

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

Usted podría llegar a funcionar con una más simple grouper() función:

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

Muchas gracias. Todas las respuestas proporcionadas fueron excelentes, pero su código fue el más rápido cuando se ejecutó en un archivo de 2.2 Gb (incluso más rápido que la versión de itertools) y es fácil de leer y de prueba unitaria. Me estoy pateando a mí mismo por no pensar en mirar itertools, hay tantas cosas buenas ahí. – Johnsyweb

+2

@Johnsyweb: Excelentes noticias sobre el rendimiento. Estoy algo orgulloso de este, y ya estaba contento de haber finalmente determinado una forma bastante elegante de hacerlo, ya que es algo que a menudo encuentro necesario en mi propio código Python cotidiano. – martineau

6
No

mucho mejor que simplemente more efficient...

Full explanation

+0

Guau, esto es genial. – Kabie

+4

El truco aquí es usar multiplicación de lista y '* args'" desreferenciación "para asegurar que el mismo objeto pase para ambos parámetros a' zip', de modo que el estado del iterador se comparte y avanza dos veces cada vez que 'zip' crea un nuevo tupla de salida. Podemos hacer esto de otras maneras: 'x = iter (l); zip (x, x) 'es quizás más legible; '(lambda x: zip (x, x)) (iter (l))' quizás más familiar para la gente de programación funcional, aunque de esta manera está casi diseñado para pretender que estamos programando sin efectos secundarios cuando de hecho estamos completamente dependiente de uno;) –

+0

@Karl Knechtel: en lugar de '(lambda x: zip (x, x)) (iter (x))' se podría usar '(lambda x = iter (x): zip (x, x))))() 'que es posiblemente un poco más legible aunque aún depende de un efecto secundario [diferente]. – martineau

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

+0

Desafortunadamente, ya es demasiado tarde para deshacer mi voto positivo al descubrir que se trata de una copia literal de una de las [recetas] (http://docs.python.org/librar/itertools.html?highlight=grouper #recipes) en los documentos 'itertools', o lo que debería llamarse plagiado ya que no se da referencia o cita alguna. – martineau

+3

@martineau: Él tiene un pequeño enlace con la etiqueta "fuente" debajo. –

+0

@Ignacio Vazquez-Abrams: Oh ... obviamente me lo perdí, lo siento @robert, pero creo que no merece un voto positivo. – martineau

1

Si vamos a lo abstracto en una función de todos modos, no es demasiado difícil escribir "desde cero":

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

versión de la receta de Robert definitivamente gana puntos para la flexibilidad , aunque.

+0

FWIW, no es "la receta de Robert", vea mi comentario en su [respuesta] (http://stackoverflow.com/questions/4356329/creating-a-python-dictionary-from-a-line-of-text/4356368 # 4356368). – martineau

Cuestiones relacionadas