Начнём
Имеется SQL база данных. Задача описывается тремя фразами:
- выгрузка данных
- валидация данных
- генерация отчёта
Задача детальнее
- Скрипт должен выполняться очень часто.
- Выгрузка данных заключается в вычитке из базы результат простейшего запроса SELECT * FROM table. В таблице/вьюшке строк обычно более 100000, колонок ~100.
- Валидация представляет собой проверку набора условий вида rowObject.Column1 == Value (<, >, !=) и более сложных проверок. Смысл в том что проверка требует обращения к колонке по имени.
- Генерация отчёта по результату проверок.
Обратим внимание на пункт 1
Остальное не так интересно.
(В качестве примера использую базу данных sqlite)
Использовать какую либо ORM для такой задачи по меньшей мере странно. Делаем в лоб (для упрощения в память выгружаем весь результат)
import sqlite3
conn = sqlite3.connect(filePath)
result = tuple(row for row in conn.cursor().execute("SELECT * FROM test"))
После выполнения result содержит кортеж кортежей. Нам же нужен объект с аттрибутами.
Усложняем:
ColsCount = 100
class RowWrapper(object):
def __init__(self, values):
self.Id = values[0]
for x in xrange(ColsCount):
setattr(self, "Col{0}".format(x), values[x + 1])
result = tuple(RowWrapper(row) for row in conn.cursor().execute(self.query))
Мы готовы перейти к пункту 2. Или нет? А давайте замеряем скорость обоих примеров(полный тестовый код здесь).
100000 строк, 101 колонка
У меня получилось в секундах:
Sample 1: 4.64823588605
Sample 2: 17.1091031498
На создание инстансов класса тратится > 10сек
С++ программисту внутри меня захотелось с этим что-нибудь сделать.
Решение нашлось такое
Используем namedtuple из модуля collections. Не буду описывать здесь подробно принцип его работы. Приведу лишь небольшой пример демонстрирующий нужную нам функциональность.
import collections
columns = ('name', 'age', 'story')
values = ('john', '99', '...blahblah...')
SuperMan = collections.namedtuple('SuperMan', columns)
firstSuperMan = SuperMan._make(values)
print(firstSuperMan.name)
print(firstSuperMan.age)
print(firstSuperMan.story)
А теперь пример в контексте задачи:
import collections
columns = tuple(itertools.chain(('Id',), tuple("Col{0}".format(x) for x in xrange(ColsCount))))
TupleClass = collections.namedtuple("TupleClass", Columns)
result = tuple(TupleClass._make(row) for row in conn.cursor().execute(self.query))
Замеряем скорость:
Sample 1: 4.30456730876
Sample 2: 15.3314512807
Sample 3: 4.67008026138
Совсем другое дело. Полный пример кода с созданием базы и замерами скорости смотрим здесь
Для примеров в статье использовалось
- python 2.7.*
- Generator expressions
- collections.namedtuple
- itertools.chain
Автор: tbd