2010-12-04 14 views
8

Ich habe eine generierte Datei mit Tausenden von Zeilen wie folgt aus:Wie erstelle ich ein Wörterbuch aus einer Textzeile?

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

einige Linien haben mehr Felder und andere haben weniger, aber alle das gleiche Muster von Schlüssel-Wert-Paare folgen und jede Zeile hat eine TSN-Feld

Wenn auf der Datei eine Analyse zu machen, ich schrieb eine Schleife wie folgt aus der Datei in ein Wörterbuch zu lesen:

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

... und das ist gut und tut genau das, was ich es (das will print ist nur ein triviales Beispiel).

aber es fühlt sich nicht besonders „pythonic“ für mich und die Zeile mit: (wie oft hat es über die Felder iterieren)

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

Welche fühlt sich einfach „klobig“.

Gibt es einen besseren Weg, dies 2.6 nur mit den Standardmodulen in Python zu tun, zur Hand?

+0

Ich denke, das ist so pythisch wie es nur geht. –

+0

Sind Sie nur an TSN-Aufzeichnungen interessiert? Oder beabsichtigen Sie, dies auf alle Datensatztypen auszudehnen? – marcog

Antwort

18

In Python 2 Sie izip im itertools Modul und die Magie des Generators nutzen könnten Objekte eine eigene Funktion schreiben um die Erstellung von Wertepaaren für die dict Datensätze zu vereinfachen. Ich habe die Idee für pairwise() von einer ähnlichen Namen (aber funktionell verschieden) recipe in dem Python 2 itertools docs.

Um den Ansatz in Python 3 verwenden Sie können einfach nur zip() verwenden, da es das tut, was izip() in Python hat 2 bei der Entfernung des letzteren aus itertools — nachstehendem Beispiel dieser Adressen und soll in beiden Versionen arbeiten.

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) 

Welche Dateien lesen for Schleife wie dies in verwendet werden können:

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

Aber warten Sie, ist da!

Es ist möglich, eine generalisierte Version erstellen Ich grouper() nennen werde, die einen ähnlichen Namen entspricht wieder, aber funktionell verschiedene itertools Rezept (die unter pairwise() rechts aufgeführt wird):

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

Welche sein könnte wie dies in Ihrer for Schleife verwendet:

record = dict(grouper(2, fields)) 

natürlich, für bestimmte Fälle wie diesen, ist es einfach zu bedienen functools.partial() und erstellen Sie eine ähnliche pairwise() Funktion mit ihm (die sowohl in Python 2 & 3 funktioniert):

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

Postscript

Es sei denn, es gibt eine wirklich große Anzahl von Feldern, könnten Sie stattdessen eine tatsächliche Sequenz erstellen aus den Paaren von Positionen (anstatt einen Generator Ausdruck verwendet, der keine len() hat):

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

Sie könnten mit einem erhalten, indem einfacher grouper() Funktion:

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

Vielen Dank. Alle Antworten waren hervorragend, aber Ihr Code war der schnellste, wenn er über eine 2,2-GB-Datei lief (sogar schneller als die itertools-Version) und einfach zu lesen und zu testen ist. Ich trete mich selbst, weil ich nicht daran gedacht habe, auf Iertools zu schauen, da sind so viele gute Sachen drin. – Johnsyweb

+2

@Johnsyweb: Ausgezeichnete Nachrichten über die Leistung. Ich bin ein bisschen stolz auf diese Sache und war schon froh, endlich einen ziemlich eleganten Weg gefunden zu haben, denn das ist etwas, was ich häufig in meinem eigenen Python-Code brauche. – martineau

6

nicht so viel besser als nur more efficient...

Full explanation

+0

Wow, das ist cool. – Kabie

+4

Der Trick hier ist die Verwendung von Listenmultiplikation und '* args'" Dereferenzierung ", um sicherzustellen, dass das gleiche Objekt für beide Parameter an' zip' übergeben wird, so dass der Iteratorstatus zweimal geteilt und jedes Mal erweitert wird, wenn 'zip' ein neues erzeugt Ausgabetupel. Wir können dies auf andere Arten tun: 'x = iter (l); zip (x, x) 'ist vielleicht besser lesbar; '(Lambda x: zip (x, x)) (iter (l))' vielleicht besser für die funktional-programmierenden Leute bekannt, obwohl dieser Weg fast so programmiert ist, dass wir ohne Nebenwirkungen programmieren, obwohl wir es sind völlig abhängig von einem;) –

+0

@Karl Knechtel: anstelle von '(Lambda x: zip (x, x)) (iter (x))' könnte man '(lambda x = iter (x): zip (x, x))() 'das ist wohl etwas besser lesbar, obwohl immer noch von einem [anderen] Nebeneffekt abhängig. – 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

Leider ist es zu spät für mich, meine Abstimmung aufzuheben, nachdem ich entdeckt habe, dass es nur eine wörtliche Kopie eines der [Rezepte] ist (http://docs.python.org/librar/itertools.html?highlight=grouper) #recipes) in den "itertools" -Dokumenten - oder was ich plagiieren würde, da kein Verweis oder Zitat angegeben ist. – martineau

+3

@martineau: Er hat einen winzigen Link namens "source" darunter. –

+0

@Ignacio Vazquez-Abrams: Oh ... offensichtlich habe ich das verpasst - Entschuldigung @robert - glaube immer noch nicht, dass es eine Abstimmung verdient hätte. – martineau

1

Wenn wir es in eine Funktion zu abstrahieren wie auch immer, es ist nicht zu schwer zu schreiben „from scratch“ gehen:

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

robert Rezept Version auf jeden Fall gewinnt Punkte für Flexibilität obwohl.

+0

FWIW, es ist nicht "Roberts Rezept", siehe meinen Kommentar unter seiner [Antwort] (http://stackoverflow.com/questions/4356329/creating-a-python-dictionary-from-a-line-of-text/4356368 # 4356368). – martineau

Verwandte Themen