Seite 2 von 2

Re: Pipeline- Funktionen?

Verfasst: Samstag 4. Dezember 2010, 18:49
von BlackJack
Achtung! Komplett ungetestet!

Code: Alles auswählen

from operator import itemgetter


class MetaData(object):
    def __init__(self, column_names):
        self.names = column_names
        self.name2index = dict((n, i) for i, n in enumerate(column_names))
    
    def __len__(self):
        return len(self.names)


class Record(object):
    def __init__(self, meta_data, values):
        self.meta_data = meta_data
        self.values = values
    
    def __len__(self):
        return len(self.values)
    
    def __getitem__(self, index):
        if isinstance(key, basestring):
            index = self.meta_data.name2index[key]
        return self.values[index]
    
    __getattr__ = __getitem__


class CSVReader(object):
    def __init__(self, filename, column_names=None, types=None, delimiter=';'):
        self.lines = open(filename, 'r')
        if column_names is None:
            column_names = self.lines.readline().split(self.delimiter)
        self.meta_data = MetaData(column_names)
        if types is None:
            types = [str] * len(self.meta_data)
        self.types = types
    
    def __iter__(self):
        for line in self.lines:
            values = tuple(
                f(v) for f, v in zip(self.types, line.split(self.delimiter))
            )
            yield Record(self.meta_data, values)
    
    def close(self):
        self.lines.close()


class ColumnSelector(object):
    def __init__(self, columns, records):
        self.records = records
        meta_data = records.meta_data
        column_names = list()
        self.indices = list()
        for column in columns:
            if isinstance(column, basestring):
                column_names.append(column)
                self.indices.append(meta_data.name2index[column])
            else:
                column_names.append(meta_data.names[column])
                self.indices.append(column)
        self.meta_data = MetaData(column_names)
    
    def __iter__(self):
        select = itemgetter(self.indices)
        return (Record(self.meta_data, select(r)) for r in self.records)

Re: Pipeline- Funktionen?

Verfasst: Sonntag 5. Dezember 2010, 00:54
von ravenheart
Hmm, ich sehe noch nicht, wie man den Columnselector verwenden kann.
Müsste der CSV-Reader eine Funktion haben, die einen ColumnSelector erstellt? Aber wofür... ich erkenne das nocht nicht ganz

Record scheint einen einzelnen Datensatz zu repräsentieren.
MetaData besteht aus den Spaltennamen und einer Zuordung zur Position

Müsste types nicht auch in die Metadaten?

Ich bitte um Entschuldigung für meine Begriffsstutzigkeit ^^

Re: Pipeline- Funktionen?

Verfasst: Sonntag 5. Dezember 2010, 09:16
von BlackJack
@ravenheart: `types` könnte auch in `MetaData`, aber da bräuchte man dann IMHO auch echte Klassen für die Typen, die beide Richtungen der Konvertierung beherrschen. Also zum Beispiel eine `Float`-Klasse die für's Schreiben in eine Datei am Ende die Fliesskommazahl auch wieder in eine Zeichenkette umwandeln kann. So wie's jetzt ist, sind die `types` im Grunde falsch benannt, und sollten `conversion_funcs` oder so heissen.

Wie gesagt es ist komplett ungetestet. Wenn es funktioniert hätte, wäre die Benutzung folgendermassen:

Code: Alles auswählen

from contextlib import closing

with closing(CSVReader('test.csv')) as rows:
    for row in ColumnSelector(['id', 'x', 'y'], rows):
        print row[0], row['x'], row.y
Da Du ja gerne diesen "train wreck"-Aufrufstil mit Methoden hättest, habe ich das jetzt mal hier eingebaut: http://paste.pocoo.org/show/300730/

Testdaten sind hier: http://paste.pocoo.org/show/300731/

Edit: `Query._query_wrap()` kann weg, die war noch ein Relikt aus dem ersten Ansatz den ich implementiert habe.

Re: Pipeline- Funktionen?

Verfasst: Sonntag 5. Dezember 2010, 22:31
von ravenheart
Ziemlich schwer zu verstehen für mich.

Konkrete Fragen hab ich dazu:

Code: Alles auswählen

@classmethod
    def register(cls, name):
        def inner(func):
            cls.methods[name] = func
            return func
        return inner

@Query.register('select')
class ColumnSelector(Query):
    ...
Mit Decorators habe ich ohnehin meine Probleme, aber könntest du die Wirkung dieser Zeilen bitte erklären?
Vielleicht mal ohne Decorator hinschreiben?^^

Re: Pipeline- Funktionen?

Verfasst: Montag 6. Dezember 2010, 10:41
von BlackJack
@ravenheart: `classmethod()` macht aus einer Methode eine Klassenmethode, dass heisst eine die man auf der Klasse aufrufen kann und die statt eines Exemplars die Klasse automatisch als erstes Argument erhält, auf der die Methode aufgerufen wurde. Haupteinsatzzweck dürften alternative "Kontruktoren" sein. Dafür benutze ich das jedenfalls mit Abstand am häufigsten.

Ohne die Dekorator-Syntax sähe der Quelltextauschnitt so aus:

Code: Alles auswählen

    def register(cls, name):
        def inner(func):
            cls.methods[name] = func
            return func
        return inner
    register = classmethod(register)

class ColumnSelector(Query):
    # ...
ColumnSelector = Query.register('select')(ColumnSelector)
Das mit dem `register()` könnte man ohne Dekorator einfacher schreiben, weil beim Dekorator das Klassenobjekt ja einfach unverändert zurück gegeben wird. Aber dann stünde die Information halt irgendwo "versteckt" hinter der Klassendefinition.

Hinter dem ``@`` muss ein beliebiger Python-Ausdruck stehen, der eine Funktion ergibt, die das zu dekorierende Objekt bekommt, und das dekorierte Objekt zurück gibt, was dann an den gleichen Namen gebunden wird, an den vorher das undekorierte Objekt gebunden war.

Es passiert hier also folgendes: Die Klasse `ColumnSelector` wird erstellt. `Query.register('select')` wird ausgeführt und das gibt die `inner()`-Funktion zurück. Die wird mit dem Klassenobjekt `ColumnSelector` aufgerufen und trägt in `Query.methods` das Wertepaar ('select', <class ColumnSelector>) ein. Die Funktion gibt das Klassenobjekt `ColumnSelector` unverändert zurück was wieder an den Namen `ColumnSelector` gebunden wird.