Ленивый оценил реализацию запроса для поиска через объекты Python, вдохновленные Django Querysets.
Во многие минуты наших задач по программированию мы должны отфильтровать иеры в поисках правильных объектов в правильном порядке. Я понял, что в большинстве случаев код времени выглядит почти таким же, но какой интерфейс будет проще всего использовать? В этот момент я понял, что реализация Django Querysets довольно удобна и хорошо известна.
Поэтому я решил написать небольшой двигатель запросов, что интерфейс будет похож на Django One. Но это будет работать для объектов Python. Дополнительным предположением было то, что оно будет лениво оценено, чтобы избежать потребления памяти.
Вся идея реле на ключевых словах аргументы формат именования. Давайте рассмотрим следующий QualName attr1.attr2 который мы можем использовать, чтобы получить или установить значение для атрибута. Этот двигатель делает вещи аналогично, но вместо того, чтобы разделить точку ( . ) Мы разделяем __ знаки. Таким образом, пример выше может быть преобразован в имя аргумента ключевого слова, подобное attr1__attr2 . Из -за того, что мы не можем использовать . В именах аргументов.
Для некоторых методов, таких как filter и exclude , мы также можем указать компаратор. По умолчанию эти методы сравниваются с равенством == . Но мы можем легко изменить это. Если мы хотим сравнить, используя <= мы можем использовать __le или __lte Postfix. Таким образом, мы получим имя аргумента, например, attr1__attr2__lt .
Все поддерживаемые компараторы описаны здесь в разделе поддерживаемых компараторов.
pip install smort-query from smort_query import ObjectQuery
# or by alias
from smort_query import OQ Каждый метод в ObjectQuery создает новый запрос. Что делает цепочку очень легкой. Самое главное, что экземпляры ObjectQuery не оцениваются - это означает, что они не загружают объекты в память, даже когда мы их цепляем.
Наборы запросов могут быть оценены несколькими способами:
Итерация:
query = ObjectQuery ( range ( 5 ))
for obj in query :
print ( obj )
"""out:
1
2
3
4
5
"""Проверка длина:
query = ObjectQuery ( range ( 10 ))
len ( query )
"""out:
10
"""Реверсирование запроса:
query = ObjectQuery ( range ( 10 ))
query . reverse ()
"""out:
<ObjectQuery for <reversed object at 0x04E8B460>>
"""
list ( list ( query . reverse ()))
"""out
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
"""Получение предметов:
query = ObjectQuery ( range ( 10 ))
query [ 5 ]
"""out:
5
""" query = ObjectQuery ( range ( 10 ))
query [ 5 : 0 : - 1 ]
"""out:
<ObjectQuery for <generator object islice_extended at 0x0608B338>>
"""
list ( query [ 5 : 0 : - 1 ])
"""out:
[5, 4, 3, 2, 1]
"""Инициализация других объектов, которые использовали итераторы/итерации (это все еще почти тот же механизм, как нормальная итерация):
query1 = ObjectQuery ( range ( 10 ))
query2 = ObjectQuery ( range ( 10 ))
list ( query1 )
"""out:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
"""
tuple ( query2 )
"""out:
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
"""Давайте рассмотрим падение кода для заполнения фальшивых людей:
from random import randint , choice
class Human :
def __init__ ( self , name , age , sex , height , weight ):
self . name = name
self . age = age
self . sex = sex
self . height = height
self . weight = weight
def __repr__ ( self ):
return str ( self . __dict__ )
def make_random_human ( name ):
return Human (
name = name ,
age = randint ( 20 , 80 ),
sex = choice (( 'female' , 'male' )),
height = randint ( 160 , 210 ),
weight = randint ( 60 , 80 ),
)Создание 10 случайных людей:
humans = [ make_random_human ( i ) for i in range ( 10 )]
"""out:
[{'name': 0, 'age': 24, 'sex': 'female', 'height': 161, 'weight': 71},
{'name': 1, 'age': 33, 'sex': 'female', 'height': 205, 'weight': 67},
{'name': 2, 'age': 45, 'sex': 'female', 'height': 186, 'weight': 74},
{'name': 3, 'age': 48, 'sex': 'female', 'height': 173, 'weight': 78},
{'name': 4, 'age': 73, 'sex': 'male', 'height': 174, 'weight': 62},
{'name': 5, 'age': 75, 'sex': 'male', 'height': 189, 'weight': 77},
{'name': 6, 'age': 64, 'sex': 'male', 'height': 179, 'weight': 63},
{'name': 7, 'age': 35, 'sex': 'female', 'height': 170, 'weight': 75},
{'name': 8, 'age': 64, 'sex': 'male', 'height': 188, 'weight': 72},
{'name': 9, 'age': 43, 'sex': 'female', 'height': 198, 'weight': 78}]
"""Обнаружение людей в возрасте между возрастом [30; 75). К этому мы будем использовать специализированные компараторы:
list ( ObjectQuery ( humans ). filter ( age__ge = 30 , age__lt = 75 ))
"""out:
[{'name': 1, 'age': 33, 'sex': 'female', 'height': 205, 'weight': 67},
{'name': 2, 'age': 45, 'sex': 'female', 'height': 186, 'weight': 74},
{'name': 3, 'age': 48, 'sex': 'female', 'height': 173, 'weight': 78},
{'name': 4, 'age': 73, 'sex': 'male', 'height': 174, 'weight': 62},
{'name': 6, 'age': 64, 'sex': 'male', 'height': 179, 'weight': 63},
{'name': 7, 'age': 35, 'sex': 'female', 'height': 170, 'weight': 75},
{'name': 8, 'age': 64, 'sex': 'male', 'height': 188, 'weight': 72},
{'name': 9, 'age': 43, 'sex': 'female', 'height': 198, 'weight': 78}]
"""Мы также можем исключить мужчин аналогичным образом:
list ( ObjectQuery ( humans ). exclude ( sex = "male" ))
"""out:
[{'name': 0, 'age': 24, 'sex': 'female', 'height': 161, 'weight': 71},
{'name': 1, 'age': 33, 'sex': 'female', 'height': 205, 'weight': 67},
{'name': 2, 'age': 45, 'sex': 'female', 'height': 186, 'weight': 74},
{'name': 3, 'age': 48, 'sex': 'female', 'height': 173, 'weight': 78},
{'name': 7, 'age': 35, 'sex': 'female', 'height': 170, 'weight': 75},
{'name': 9, 'age': 43, 'sex': 'female', 'height': 198, 'weight': 78}]
""" Заказ sex атрибутами в порядке восходящего порядка:
list ( ObjectQuery ( humans ). order_by ( "sex" ))
"""out
[{'name': 0, 'age': 24, 'sex': 'female', 'height': 161, 'weight': 71},
{'name': 1, 'age': 33, 'sex': 'female', 'height': 205, 'weight': 67},
{'name': 2, 'age': 45, 'sex': 'female', 'height': 186, 'weight': 74},
{'name': 3, 'age': 48, 'sex': 'female', 'height': 173, 'weight': 78},
{'name': 7, 'age': 35, 'sex': 'female', 'height': 170, 'weight': 75},
{'name': 9, 'age': 43, 'sex': 'female', 'height': 198, 'weight': 78},
{'name': 4, 'age': 73, 'sex': 'male', 'height': 174, 'weight': 62},
{'name': 5, 'age': 75, 'sex': 'male', 'height': 189, 'weight': 77},
{'name': 6, 'age': 64, 'sex': 'male', 'height': 179, 'weight': 63},
{'name': 8, 'age': 64, 'sex': 'male', 'height': 188, 'weight': 72}]
""" Заказ sex атрибутами в порядке убывания:
list ( ObjectQuery ( humans ). order_by ( "-sex" ))
"""out
[{'name': 4, 'age': 73, 'sex': 'male', 'height': 174, 'weight': 62},
{'name': 5, 'age': 75, 'sex': 'male', 'height': 189, 'weight': 77},
{'name': 6, 'age': 64, 'sex': 'male', 'height': 179, 'weight': 63},
{'name': 8, 'age': 64, 'sex': 'male', 'height': 188, 'weight': 72},
{'name': 0, 'age': 24, 'sex': 'female', 'height': 161, 'weight': 71},
{'name': 1, 'age': 33, 'sex': 'female', 'height': 205, 'weight': 67},
{'name': 2, 'age': 45, 'sex': 'female', 'height': 186, 'weight': 74},
{'name': 3, 'age': 48, 'sex': 'female', 'height': 173, 'weight': 78},
{'name': 7, 'age': 35, 'sex': 'female', 'height': 170, 'weight': 75},
{'name': 9, 'age': 43, 'sex': 'female', 'height': 198, 'weight': 78}]
"""Заказ по нескольким атрибутам:
list ( ObjectQuery ( humans ). order_by ( "-sex" , "height" ))
"""out:
[{'name': 5, 'age': 75, 'sex': 'male', 'height': 189, 'weight': 77},
{'name': 8, 'age': 64, 'sex': 'male', 'height': 188, 'weight': 72},
{'name': 6, 'age': 64, 'sex': 'male', 'height': 179, 'weight': 63},
{'name': 4, 'age': 73, 'sex': 'male', 'height': 174, 'weight': 62},
{'name': 1, 'age': 33, 'sex': 'female', 'height': 205, 'weight': 67},
{'name': 9, 'age': 43, 'sex': 'female', 'height': 198, 'weight': 78},
{'name': 2, 'age': 45, 'sex': 'female', 'height': 186, 'weight': 74},
{'name': 3, 'age': 48, 'sex': 'female', 'height': 173, 'weight': 78},
{'name': 7, 'age': 35, 'sex': 'female', 'height': 170, 'weight': 75},
{'name': 0, 'age': 24, 'sex': 'female', 'height': 161, 'weight': 71}]
"""Если некоторые атрибуты, на которые стоит фильтрации и упорядочения, недоступны вручную, мы можем рассчитать их на лету:
# Sorry for example if someone feels offended
root_query = ObjectQuery ( humans )
only_females = root_query . filter ( sex = "female" ) # reduce objects for annotation calculation
bmi_annotated_females = only_females . annotate ( bmi = lambda obj : obj . weight / ( obj . height / 100 ) ** 2 )
overweight_females = bmi_annotated_females . filter ( bmi__gt = 25 )
overweight_females_ordered_by_age = overweight_females . order_by ( "age" )
list ( overweight_females_ordered_by_age )
"""out:
[{'name': 0, 'age': 24, 'sex': 'female', 'height': 161, 'weight': 71, 'bmi': 27.390918560240728},
{'name': 7, 'age': 35, 'sex': 'female', 'height': 170, 'weight': 75, 'bmi': 25.95155709342561},
{'name': 3, 'age': 48, 'sex': 'female', 'height': 173, 'weight': 78, 'bmi': 26.061679307694877}]
"""Каждый запрос метода возвращает копию. Там, где итерация над вновь созданными не влияет на источники объектов.
root_query = ObjectQuery ( humans ). filter ( age__ge = 30 , age__lt = 75 )
query1 = root_query . filter ( weight__gt = 75 )
query2 = root_query . filter ( weight__in = [ 78 , 62 ])
list ( query1 )
"""out:
[{'name': 3, 'age': 48, 'sex': 'female', 'height': 173, 'weight': 78},
{'name': 9, 'age': 43, 'sex': 'female', 'height': 198, 'weight': 78}]
"""
list ( query2 )
"""out:
[{'name': 3, 'age': 48, 'sex': 'female', 'height': 173, 'weight': 78},
{'name': 4, 'age': 73, 'sex': 'male', 'height': 174, 'weight': 62},
{'name': 9, 'age': 43, 'sex': 'female', 'height': 198, 'weight': 78}]
"""
list ( root_query )
"""out:
[{'name': 1, 'age': 33, 'sex': 'female', 'height': 205, 'weight': 67},
{'name': 2, 'age': 45, 'sex': 'female', 'height': 186, 'weight': 74},
{'name': 3, 'age': 48, 'sex': 'female', 'height': 173, 'weight': 78},
{'name': 4, 'age': 73, 'sex': 'male', 'height': 174, 'weight': 62},
{'name': 6, 'age': 64, 'sex': 'male', 'height': 179, 'weight': 63},
{'name': 7, 'age': 35, 'sex': 'female', 'height': 170, 'weight': 75},
{'name': 8, 'age': 64, 'sex': 'male', 'height': 188, 'weight': 72},
{'name': 9, 'age': 43, 'sex': 'female', 'height': 198, 'weight': 78}]
""" Но иногда оценка некоторого запроса в середине цепи может сломать его, поэтому, если вы явно хотите сохранить что -нибудь копию запроса и убедитесь, что дальнейшие действия на root не повлияют на запрос, вы можете сделать:
root_query = ObjectQuery ( humans )
copy = root_query . all ()Вы также можете изменить запрос, но помните, что он оценит запрос:
root_query = ObjectQuery ( humans ). reverse ()
list ( root_query )
"""out:
[{'name': 9, 'age': 43, 'sex': 'female', 'height': 198, 'weight': 78},
{'name': 8, 'age': 64, 'sex': 'male', 'height': 188, 'weight': 72},
{'name': 7, 'age': 35, 'sex': 'female', 'height': 170, 'weight': 75},
{'name': 6, 'age': 64, 'sex': 'male', 'height': 179, 'weight': 63},
{'name': 5, 'age': 75, 'sex': 'male', 'height': 189, 'weight': 77},
{'name': 4, 'age': 73, 'sex': 'male', 'height': 174, 'weight': 62},
{'name': 3, 'age': 48, 'sex': 'female', 'height': 173, 'weight': 78},
{'name': 2, 'age': 45, 'sex': 'female', 'height': 186, 'weight': 74},
{'name': 1, 'age': 33, 'sex': 'female', 'height': 205, 'weight': 67},
{'name': 0, 'age': 24, 'sex': 'female', 'height': 161, 'weight': 71}]
""" Кубики или объединяет два запроса вместе. То же, что и метод union . Обратите внимание, что после Oring два запроса или даже больше, заказа может потребоваться:
root_query = ObjectQuery ( humans )
males = root_query . filter ( sex = "male" )
females = root_query . filter ( sex = "female" )
both1 = ( males | females )
both2 = males . union ( females )
list ( both1 )
"""out:
[{'name': 4, 'age': 73, 'sex': 'male', 'height': 174, 'weight': 62},
{'name': 5, 'age': 75, 'sex': 'male', 'height': 189, 'weight': 77},
{'name': 6, 'age': 64, 'sex': 'male', 'height': 179, 'weight': 63},
{'name': 8, 'age': 64, 'sex': 'male', 'height': 188, 'weight': 72},
{'name': 0, 'age': 24, 'sex': 'female', 'height': 161, 'weight': 71},
{'name': 1, 'age': 33, 'sex': 'female', 'height': 205, 'weight': 67},
{'name': 2, 'age': 45, 'sex': 'female', 'height': 186, 'weight': 74},
{'name': 3, 'age': 48, 'sex': 'female', 'height': 173, 'weight': 78},
{'name': 7, 'age': 35, 'sex': 'female', 'height': 170, 'weight': 75},
{'name': 9, 'age': 43, 'sex': 'female', 'height': 198, 'weight': 78}]
"""
list ( both2 )
"""out:
[{'name': 4, 'age': 73, 'sex': 'male', 'height': 174, 'weight': 62},
{'name': 5, 'age': 75, 'sex': 'male', 'height': 189, 'weight': 77},
{'name': 6, 'age': 64, 'sex': 'male', 'height': 179, 'weight': 63},
{'name': 8, 'age': 64, 'sex': 'male', 'height': 188, 'weight': 72},
{'name': 0, 'age': 24, 'sex': 'female', 'height': 161, 'weight': 71},
{'name': 1, 'age': 33, 'sex': 'female', 'height': 205, 'weight': 67},
{'name': 2, 'age': 45, 'sex': 'female', 'height': 186, 'weight': 74},
{'name': 3, 'age': 48, 'sex': 'female', 'height': 173, 'weight': 78},
{'name': 7, 'age': 35, 'sex': 'female', 'height': 170, 'weight': 75},
{'name': 9, 'age': 43, 'sex': 'female', 'height': 198, 'weight': 78}]
""" Проект поддерживает многих компараторов, которые можно выбрать в качестве постфикса для поиска:
eqeq делает a == bexact делает a == bin Make a in bcontains марки b in agt делает a > bgte делает a >= bge делает a >= blt делает a < blte делает a <= ble делает a <= b asc() и desc() , которые работают так же, как order_by() но с указанным порядком заранее.unique_justseen() и unique_everseen() для удаления дубликатов. Сравнение, реализованное с помощью передаваемых атрибутов или делегированных в равенство объектов __eq__ .intersection() для поиска общих объектов в двух запросах. Сравнение, реализованное с помощью передаваемых атрибутов или делегированных в равенство объектов __eq__ .__len__ и __getitem__ для оценки запроса только один раз за жизненный цикл. Любая форма вклада ценится. Поиск проблем, новых идей, новых функций. И, конечно, вы можете создать PR для этого проекта.