Mein Szenario lautet wie folgt: Ich habe eine Datentabelle (eine Handvoll Felder, weniger als hundert Zeilen), die ich in meinem Programm häufig verwende. Ich brauche diese Daten auch, um dauerhaft zu sein, also speichere ich sie als CSV und lade sie beim Start. Ich entscheide mich, keine Datenbank zu verwenden, da jede Option (auch SQLite) ein Overkill für meine bescheidene Anforderung ist (auch - ich möchte die Werte auf einfache Weise offline bearbeiten können und nichts ist einfacher als der Editor).
Angenommen, meine Daten sehen wie folgt aus (in der Datei ist das Komma ohne Titel getrennt, dies ist nur eine Illustration):
Row | Name | Year | Priority
------------------------------------
1 | Cat | 1998 | 1
2 | Fish | 1998 | 2
3 | Dog | 1999 | 1
4 | Aardvark | 2000 | 1
5 | Wallaby | 2000 | 1
6 | Zebra | 2001 | 3
Anmerkungen:
- Zeile kann ein "realer" Wert sein, der in die Datei geschrieben wird, oder nur ein automatisch generierter Wert, der die Zeilennummer darstellt. So oder so existiert es im Speicher.
- Namen sind einzigartig.
Dinge, die ich mit den Daten mache:
- Suchen Sie eine Zeile basierend auf ID (Iteration) oder Name (direkter Zugriff).
- Zeigen Sie die Tabelle in verschiedenen Reihenfolgen an, basierend auf mehreren Feldern: Ich muss sie z. B. nach Priorität und dann nach Jahr oder nach Jahr und dann nach Priorität usw. sortieren.
- Ich muss Instanzen basierend auf Parametersätzen zählen, z. B. wie viele Zeilen ihr Jahr zwischen 1997 und 2002 haben oder wie viele Zeilen 1998 und Priorität> 2 usw. sind.
Ich weiß, dass dies nach SQL "schreit" ...
Ich versuche herauszufinden, was die beste Wahl für die Datenstruktur ist. Es folgen verschiedene Möglichkeiten, die ich sehe:
Liste der Zeilenlisten:
a = []
a.append( [1, "Cat", 1998, 1] )
a.append( [2, "Fish", 1998, 2] )
a.append( [3, "Dog", 1999, 1] )
...
Liste der Spaltenlisten (es wird offensichtlich eine API für add_row usw. geben):
a = []
a.append( [1, 2, 3, 4, 5, 6] )
a.append( ["Cat", "Fish", "Dog", "Aardvark", "Wallaby", "Zebra"] )
a.append( [1998, 1998, 1999, 2000, 2000, 2001] )
a.append( [1, 2, 1, 1, 1, 3] )
Wörterbuch der Spaltenlisten (Konstanten können erstellt werden, um die Zeichenfolgenschlüssel zu ersetzen):
a = {}
a['ID'] = [1, 2, 3, 4, 5, 6]
a['Name'] = ["Cat", "Fish", "Dog", "Aardvark", "Wallaby", "Zebra"]
a['Year'] = [1998, 1998, 1999, 2000, 2000, 2001]
a['Priority'] = [1, 2, 1, 1, 1, 3]
Wörterbuch mit Schlüsseln als Tupel von (Zeile, Feld):
Create constants to avoid string searching
NAME=1
YEAR=2
PRIORITY=3
a={}
a[(1, NAME)] = "Cat"
a[(1, YEAR)] = 1998
a[(1, PRIORITY)] = 1
a[(2, NAME)] = "Fish"
a[(2, YEAR)] = 1998
a[(2, PRIORITY)] = 2
...
Und ich bin mir sicher, dass es andere Wege gibt ... Jeder Weg hat jedoch Nachteile, wenn es um meine Anforderungen geht (komplexe Bestellung und Zählung).
Was ist der empfohlene Ansatz?
BEARBEITEN:
Zur Verdeutlichung ist die Leistung für mich kein großes Problem. Da die Tabelle so klein ist, glaube ich, dass fast jede Operation im Bereich von Millisekunden liegen wird, was für meine Anwendung kein Problem darstellt.
quelle
Antworten:
Eine "Tabelle" im Speicher zu haben, die Nachschlagen, Sortieren und willkürliche Aggregation erfordert, erfordert wirklich SQL. Sie sagten, Sie hätten SQLite ausprobiert, aber haben Sie erkannt, dass SQLite eine Nur-Speicher-Datenbank verwenden kann?
connection = sqlite3.connect(':memory:')
Anschließend können Sie Tabellen im Speicher mit allen Funktionen von SQLite erstellen / löschen / abfragen / aktualisieren, ohne dass Dateien übrig bleiben, wenn Sie fertig sind. Und ab Python 2.5
sqlite3
befindet es sich in der Standardbibliothek, so dass es IMO nicht wirklich "übertrieben" ist.Hier ist ein Beispiel, wie man die Datenbank erstellen und füllen kann:
import csv import sqlite3 db = sqlite3.connect(':memory:') def init_db(cur): cur.execute('''CREATE TABLE foo ( Row INTEGER, Name TEXT, Year INTEGER, Priority INTEGER)''') def populate_db(cur, csv_fp): rdr = csv.reader(csv_fp) cur.executemany(''' INSERT INTO foo (Row, Name, Year, Priority) VALUES (?,?,?,?)''', rdr) cur = db.cursor() init_db(cur) populate_db(cur, open('my_csv_input_file.csv')) db.commit()
Wenn Sie SQL wirklich nicht verwenden möchten, sollten Sie wahrscheinlich eine Liste von Wörterbüchern verwenden:
lod = [ ] # "list of dicts" def populate_lod(lod, csv_fp): rdr = csv.DictReader(csv_fp, ['Row', 'Name', 'Year', 'Priority']) lod.extend(rdr) def query_lod(lod, filter=None, sort_keys=None): if filter is not None: lod = (r for r in lod if filter(r)) if sort_keys is not None: lod = sorted(lod, key=lambda r:[r[k] for k in sort_keys]) else: lod = list(lod) return lod def lookup_lod(lod, **kw): for row in lod: for k,v in kw.iteritems(): if row[k] != str(v): break else: return row return None
Testen ergibt dann:
>>> lod = [] >>> populate_lod(lod, csv_fp) >>> >>> pprint(lookup_lod(lod, Row=1)) {'Name': 'Cat', 'Priority': '1', 'Row': '1', 'Year': '1998'} >>> pprint(lookup_lod(lod, Name='Aardvark')) {'Name': 'Aardvark', 'Priority': '1', 'Row': '4', 'Year': '2000'} >>> pprint(query_lod(lod, sort_keys=('Priority', 'Year'))) [{'Name': 'Cat', 'Priority': '1', 'Row': '1', 'Year': '1998'}, {'Name': 'Dog', 'Priority': '1', 'Row': '3', 'Year': '1999'}, {'Name': 'Aardvark', 'Priority': '1', 'Row': '4', 'Year': '2000'}, {'Name': 'Wallaby', 'Priority': '1', 'Row': '5', 'Year': '2000'}, {'Name': 'Fish', 'Priority': '2', 'Row': '2', 'Year': '1998'}, {'Name': 'Zebra', 'Priority': '3', 'Row': '6', 'Year': '2001'}] >>> pprint(query_lod(lod, sort_keys=('Year', 'Priority'))) [{'Name': 'Cat', 'Priority': '1', 'Row': '1', 'Year': '1998'}, {'Name': 'Fish', 'Priority': '2', 'Row': '2', 'Year': '1998'}, {'Name': 'Dog', 'Priority': '1', 'Row': '3', 'Year': '1999'}, {'Name': 'Aardvark', 'Priority': '1', 'Row': '4', 'Year': '2000'}, {'Name': 'Wallaby', 'Priority': '1', 'Row': '5', 'Year': '2000'}, {'Name': 'Zebra', 'Priority': '3', 'Row': '6', 'Year': '2001'}] >>> print len(query_lod(lod, lambda r:1997 <= int(r['Year']) <= 2002)) 6 >>> print len(query_lod(lod, lambda r:int(r['Year'])==1998 and int(r['Priority']) > 2)) 0
Persönlich mag ich die SQLite-Version besser, da sie Ihre Typen besser bewahrt (ohne zusätzlichen Konvertierungscode in Python) und leicht wächst, um zukünftigen Anforderungen gerecht zu werden. Aber andererseits bin ich mit SQL ziemlich vertraut, also YMMV.
quelle
Eine sehr alte Frage, die ich kenne, aber ...
Ein Pandas DataFrame scheint hier die ideale Option zu sein.
http://pandas.pydata.org/pandas-docs/version/0.13.1/generated/pandas.DataFrame.html
Aus dem Klappentext
http://pandas.pydata.org/
quelle
Ich persönlich würde die Liste der Zeilenlisten verwenden. Da die Daten für jede Zeile immer in derselben Reihenfolge vorliegen, können Sie einfach nach jeder der Spalten sortieren, indem Sie einfach auf dieses Element in jeder der Listen zugreifen. Sie können auch einfach anhand einer bestimmten Spalte in jeder Liste zählen und auch suchen. Es ist im Grunde so nah wie an einem 2D-Array.
Der einzige Nachteil hierbei ist, dass Sie wissen müssen, in welcher Reihenfolge sich die Daten befinden. Wenn Sie diese Reihenfolge ändern, müssen Sie Ihre Such- / Sortierroutinen entsprechend ändern.
Sie können auch eine Liste mit Wörterbüchern erstellen.
rows = [] rows.append({"ID":"1", "name":"Cat", "year":"1998", "priority":"1"})
Auf diese Weise müssen Sie die Reihenfolge der Parameter nicht kennen, sodass Sie jedes Feld "Jahr" in der Liste durchsehen können.
quelle
Haben Sie eine Tabellenklasse, deren Zeilen eine Liste von diktierten oder besseren Zeilenobjekten sind
Fügen Sie in der Tabelle keine Zeilen direkt hinzu, sondern verfügen Sie über eine Methode, mit der einige Lookup-Maps aktualisiert werden, z. B. nach Namen. Wenn Sie keine Zeilen in der angegebenen Reihenfolge hinzufügen oder die ID nicht fortlaufend ist, können Sie auch idMap verwenden, z
class Table(object): def __init__(self): self.rows = []# list of row objects, we assume if order of id self.nameMap = {} # for faster direct lookup for row by name def addRow(self, row): self.rows.append(row) self.nameMap[row['name']] = row def getRow(self, name): return self.nameMap[name] table = Table() table.addRow({'ID':1,'name':'a'})
quelle
Sind Sie angesichts des komplexen Datenabrufs sicher, dass auch SQLite zu viel des Guten ist?
Sie werden eine ad hoc, informell spezifizierte, fehlerbehaftete, langsame Implementierung der Hälfte von SQLite erhalten, die die zehnte Regel von Greenspun umschreibt .
Sie haben jedoch Recht, wenn Sie sagen, dass die Auswahl einer einzelnen Datenstruktur sich auf eine oder mehrere Such-, Sortier- oder Zählvorgänge auswirkt. Wenn also die Leistung an erster Stelle steht und Ihre Daten konstant sind, können Sie in Betracht ziehen, mehr als eine Struktur für verschiedene Zwecke zu verwenden.
Messen Sie vor allem, welche Vorgänge häufiger auftreten, und entscheiden Sie, welche Struktur weniger kostet.
quelle
Ich persönlich habe eine Bibliothek geschrieben, die vor kurzem BD_XML heißt
Der grundlegendste Existenzgrund besteht darin, Daten zwischen XML-Dateien und SQL-Datenbanken hin und her zu senden.
Es ist in Spanisch geschrieben (wenn das in einer Programmiersprache wichtig ist), aber es ist sehr einfach.
from BD_XML import Tabla
Es definiert ein Objekt namens Tabla (Tabelle) und kann mit einem Namen zur Identifizierung eines vorab erstellten Verbindungsobjekts einer pep-246-kompatiblen Datenbankschnittstelle erstellt werden.
Table = Tabla('Animals')
Dann müssen Sie Spalten mit der
agregar_columna
Methode (add_column) hinzufügen, mit der verschiedene Schlüsselwortargumente verwendet werden können:campo
(Feld): Der Name des Feldestipo
(Typ): Der Typ der gespeicherten Daten kann beispielsweise 'varchar' und 'double' oder der Name von Python-Objekten sein, wenn Sie nicht daran interessiert sind, letztere in eine Datenbank zu exportieren.defecto
(Standard): Legen Sie einen Standardwert für die Spalte fest, wenn beim Hinzufügen einer Zeile keiner vorhanden istEs gibt andere 3, aber nur für Datenbanktöne und nicht wirklich funktionsfähig
mögen:
Table.agregar_columna(campo='Name', tipo='str') Table.agregar_columna(campo='Year', tipo='date') #declaring it date, time, datetime or timestamp is important for being able to store it as a time object and not only as a number, But you can always put it as a int if you don't care for dates Table.agregar_columna(campo='Priority', tipo='int')
Anschließend fügen Sie die Zeilen mit dem Operator + = hinzu (oder +, wenn Sie eine Kopie mit einer zusätzlichen Zeile erstellen möchten).
Table += ('Cat', date(1998,1,1), 1) Table += {'Year':date(1998,1,1), 'Priority':2, Name:'Fish'} #… #The condition for adding is that is a container accessible with either the column name or the position of the column in the table
Anschließend können Sie XML generieren und mit
exportar_XML
(export_XML) undescribir_XML
(write_XML) in eine Datei schreiben :file = os.path.abspath(os.path.join(os.path.dirname(__file__), 'Animals.xml')) Table.exportar_xml() Table.escribir_xml(file)
Importieren Sie es dann mit
importar_XML
(import_XML) mit dem Dateinamen und dem Hinweis, dass Sie eine Datei und kein Zeichenfolgenliteral verwenden, zurück:Table.importar_xml(file, tipo='archivo') #archivo means file
Fortgeschrittene
Auf diese Weise können Sie ein Tabla-Objekt auf SQL-Weise verwenden.
#UPDATE <Table> SET Name = CONCAT(Name,' ',Priority), Priority = NULL WHERE id = 2 for row in Table: if row['id'] == 2: row['Name'] += ' ' + row['Priority'] row['Priority'] = None print(Table) #DELETE FROM <Table> WHERE MOD(id,2) = 0 LIMIT 1 n = 0 nmax = 1 for row in Table: if row['id'] % 2 == 0: del Table[row] n += 1 if n >= nmax: break print(Table)
In diesen Beispielen wird eine Spalte mit dem Namen 'id' angenommen, die jedoch für Ihr Beispiel durch width row.pos ersetzt werden kann.
if row.pos == 2:
Die Datei kann heruntergeladen werden von:
https://bitbucket.org/WolfangT/librerias
quelle