sobota, 5 grudnia 2009

Szybkie tworzenie obiektów z wyniku zapytania SQL

Jeśli nie wykorzystujemy w Pythonie z jakiegoś powodu ORM do zarządzania danymi bazy danych, często zmuszeni jesteśmy do tworzenia długich przypisań wartości do atrybutów klas oraz panowania nad indeksami. Jakakolwiek zmiana w liczbie kolumn tabeli zmusza nas do wykonywania modyfikacji kodu w fafnastu miejscach, co może powodować błędy i straszne bóle głowy. Można jednakże wykorzystać mechanizmy, które ten proces w znacznym stopniu uproszczą.

Dla naszego przykładu stworzymy sobie tabelę o nazwie MyTable, zawierającą cztery kolumny: colA, colB, colC oraz colD. Pominę tutaj cały bajzel z tworzeniem połączenia z bazą oraz kursorów, gdyż nie jest to tematem niniejszego wpisu. Utworzymy sobie także prostą klasę-kontener, której instancje przechowywać będą wyniki zapytania SQL. Wygląda ona następująco:
class MyTableRow(object):
    def __init__(self, colA=None, colB=None,
                       colC=None, colD=None, **kwargs):
        self.colA = kwargs.get('colA', colA)
        self.colB = kwargs.get('colB', colB)
        self.colC = kwargs.get('colC', colC)
        self.colD = kwargs.get('colD', colD)
Każdy atrybut odpowiada jednej kolumnie tabeli, a dodatkowo możemy użyć w konstruktorze nazwanych parametrów (jeśli zamierzamy z nich korzystać, należy się zastanowić nad sensownym dobraniem wartości domyślnych).
Typowe zastosowanie klasy wygląda następująco:
cur.execute("""SELECT colA, colB, colC, colD
                FROM MyTable
                WHERE colA LIKE '%a%'""")

result = []
for row in cur.fetchall():
    result.append(MyTableRow(row[0], row[1], row[2], row[3]))
Musimy zatem pilnować nie tylko ilości i kolejności parametrów konstruktora klasy, lecz także indeksów tablicy zwracanej przez cur.excecute(). Sprawa nieco bardziej zaczyna się komplikować, gdy w zapytaniu SQL zmienimy kolejność nazw selektora kolumn, lub dodamy/usuniemy jakieś ze środka. Czeka nas przeindeksowanie całej inicjacji obiektu.
Mechanizmem, który może pomóc w uproszczeniu tego zadania (a także samego kodu) jest niezawodny tandem - funkcja anonimowa + map(). W naszym przypadku, najprostsza wersja z użyciem tej silnej konstrukcji może wyglądac tak:
result = map(lambda x: MyTableRow(*x), cur.fetchall())
W tym momencie, zmiana kolejności lub liczby kolumn w tabeli wymaga jedynie dokonania odpowiednich zmian w parametrach konstruktora klasy MyTableRow. Ponieważ daliśmy możliwość użycia nazwanych parametrów, w prosty sposób możemy wymusić ustawienie jednej z wartości na określoną przez nas, bez konieczności iterowania po całej tablicy wyników. Przykładowo, jeśli każdy z obiektów wyniku ma mieć wartość atrybutu colA ustawioną na 42, wystarczy jedynie użyć nazwanego parametru:
result = map(lambda x: MyTableRow(*x, colA=42), cur.fetchall())
Nasz konstruktor przypisuje wartości do atrybutów w kolejności: nazwany parametr, parametr. Jeśli zatem użyjemy tego pierwszego, tylko jego wartość będzie brana pod uwagę.
Należy jednakże pamiętać, iż konstruktor posiada wartości domyślne. Jeśli chcemy uniknąć tworzenia "pustych" instancji, musimy się o to zatroszczyć np. podnosząc wyjątek ValueError gdy wszystkie parametry konstruktora mają wartość None, oraz słownik kwargs jest pusty.

Brak komentarzy:

Prześlij komentarz

Uwaga. Komentarze są moderowane i mogą nie pojawić się natychmiast po utworzeniu. Autor niniejszego bloga zastrzega sobie prawo do niedopuszczenia komentarzy będących SPAMem i/lub nie odnoszących się do komentowanego wpisu i/lub łamiących zasady kulturalnej wymiany opinii.