Closed LecronRu closed 2 months ago
Да, тоже думал над этим. Не сделано только потому что в наших проектах пока что не было востребовано. А вообще, согласен, фича полезная.
Будет называться sql_iterate
для объектов. Полезность итерирования скаляров для меня сомнительна. Можете придумать юзкейс для этого?
На следующей неделе попытаюсь запилить.
Полезность ровно такая же как для объектов, когда "объект" из одного поля. Прямо сейчас идет обработка: for text, in execute('SELECT text FROM s WHERE id in (SELECT s_id FROM p WHERE word = ?)') Обратите внимание на запятую распаковки в скаляр после "for text'.
Фича не просто полезная, а основаная. Рекомендую в документации упор делать именно на нее. Дело не в востребоанности, а в best practic — функция выполняет одну задачу. Одна выполняет извлечение данных (в библиотеке), вторая преобразует в нужную последовательность — кортеж, список и даже словарь, — в клиентском коде.
Да, с точки зрения правды жизни вытаскивание всего списка объектов целиком это под капотом всё равно итерирование результатов запроса, последоватольно вынимаемых из курсора. Если нам нужно просто пробежаться по результату запроса, превращение его в список выглядит как дополнительное действие, которое не нужно. Если это не десятки-сотни гигабайт, которые съедят нам всю память, то накладные расходы пренебрежимо малы, но, тем не менее, вот это вот лишнее действие вызывает ощущение досадного несовершенства. Но давайте посмотрим на то, какое удобство мы покупаем за ничтожно малую цену.
Список датаклассов - предельно простая и предсказуемая сущность. Он не преподнесёт никаких сюрпризов. Итератор, особенно такой, который там внутри что-то дотягивает из внешней системы - гораздо более капризная штука.
Допустим, у на есть такой кусок кода:
self.orders = get_user_orders(conn, self.user_id)
Если get_user_orders
возвращает список, то:
conn
нам больше для вычитки ордеров не нужен. Если хотим, мы даже можем его закрыть.self.orders
мы дальше можем делать что хотим. Хотим - пройдёмся по нему разок-другой. Или превратим в dict. Или распилим на куски и раздадим тредам на обработку.Если же это итератор, то:
StopIteration
может быть как по причине того, что мы уже один раз его прошли, так и потому что просто нет ордеров.А если у нас async, это ещё добавит интересных нюансов.
Короче, работа через итератор - неплохой способ организовать себе минное поле на ровном месте. И первым же на нём подорваться. Главное, ради чего эта копеечная экономия?
Когда мне бывает нужно откуда-нибудь куда-нибудь переложить сотню-другую гигабайт, я обычно вместо стриминга данных просто вычитываю кусками по, например, 10000 строчек. Так оно получается надёжнее. Но я прекрасно понимаю, что если таких задач становится много, это покусочечное вычитывание начнёт напрягать. Поэтому таки да, стриминг надо сделать, но с припиской, что применять осторожно и думать дважды.
Кстати, вопрос. Может быть, сразу сделать стриминг сразу с нарезкой на чанки? Типа чтобы итератор отдавал объекты не по одному, а порциями? Или это будет лишнее усложнение?
Вы не до конца поняли. В вашем примере, при моем подходе, будет всего лишь
self.orders = list(get_user_orders(conn, self.user_id))
со всеми перечисленными вами свойствами списка. Вся разница, преобразование в список выносится на уровень пользовательского кода. Но если нам список не нужен, этого преобразования не происходит и пользуемся свойствми итератора. Да, у него есть особенности. Но они несложны и программист на Python должен их знать и учитывать.
Проходить до конца не надо. Если выходим из области видимости контекстного менеджера, соединение будет закрыто. Если же работаете без контекстного менеджера, естественно извольте закрывать вручную. Но это опять же, рядовая работа программиста. Скажу больше, в sqlite без wal-mode, можно даже наткнутся на блокировку записи в базу.
ради чего эта копеечная экономия?
Это уж пусть клиент решает, не надо лишать его выбора. Ваша библиотека лишь упорядочивает получение данных из базы данных. Не надо брать лишнего. Представьте, как бы клиент получал данные без нее. Либо итерировался, со всеми особенностями итераторов, либо делал execute().fetchall() или list(execute()).
Также и с чанками. Если память не изменяет, в more-itertools такая функция должна быть.
Короче, приделываю декораторы sql_iterate
и sql_iterate_scalars
. На выходных надеюсь закончить.
Рекомендуемый подход остаётся без итераторов, то есть через соответственно sql_fetch_all
и sql_fetch_scalars
.
Done in version 0.1.2
Часто клиенту не нужен список. Например при аггрегировании на клиенте или другой обработке множества записей. В том числе в вашем же примере. Причем множество может доходить до миллионов. Разумно вместо списка возвращать генератор, а клиент, если ему понадобится, сам сделает all_users = list(get_all_users())
Заодно можно переименовать методы в более говорящие sql_objects и sql_scalars (убрав слово fetch). А для сохранения обратной совместимости, старым методам присвоить алиасы на новые.