A Lazy avaliou a implementação de consultas para pesquisar objetos Python inspirados nos consultas de Django.
Em muitos momentos de nossas tarefas de programação, precisamos filtrar iteráveis em busca dos objetos certos em ordem certa. Percebi que na maioria das vezes o código parece quase o mesmo, mas que tipo de interface será mais fácil de usar? Naquele momento, descobri que a implementação do Django QuerySets é meio útil e bem conhecida.
Por isso, decidi escrever um pequeno mecanismo de consulta que a interface será semelhante ao Django One. Mas funcionará para objetos Python. Suposição adicional foi que será preguiçoso avaliado para evitar o consumo de memória.
Idéia inteira retransmite no formato de nomeação de argumentos de palavras -chave. Vamos considerar seguir qualname attr1.attr2 que podemos ser usados para obter ou definir valor para atributo. Esse mecanismo faz as coisas da mesma forma, mas em vez de se separar por ponto ( . ) Estamos separando por __ sinais. Portanto, o exemplo acima pode ser convertido em nome do argumento de palavras -chave como esse attr1__attr2 . Devido ao fato de que não podemos usar . em nomes de argumentos.
Para alguns métodos como filter e exclude , também podemos especificar o comparador. Por padrão, esses métodos estão se comparando contra a igualdade == . Mas podemos mudar facilmente. Se quisermos comparar usando <= podemos usar __le ou __lte postfix. Então, acabaremos com o nome do argumento como attr1__attr2__lt .
Todos os comparadores suportados são descritos aqui na seção Comparadores suportados.
pip install smort-query from smort_query import ObjectQuery
# or by alias
from smort_query import OQ Cada método no ObjectQuery produz uma nova consulta. O que facilita muito o encadeamento. O mais importante é que as instâncias ObjectQuery não são avaliadas - significa que eles não estão carregando objetos para a memória, mesmo quando os está encadeando.
Conjuntos de consultas podem ser avaliados de várias maneiras:
Iteração:
query = ObjectQuery ( range ( 5 ))
for obj in query :
print ( obj )
"""out:
1
2
3
4
5
"""Comprimento de verificação:
query = ObjectQuery ( range ( 10 ))
len ( query )
"""out:
10
"""Consulta reversa:
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]
"""Obtendo itens:
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]
"""Inicializando outros objetos que usavam iteradores/iteráveis (ainda é quase o mesmo mecanismo como a iteração normal):
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)
"""Vamos considerar o código de queda para preencher humanos falsos:
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 ),
)Criando 10 humanos aleatórios:
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}]
"""Encontrar pessoas de idade entre [30; 75). Para isso, usaremos comparadores especializados:
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}]
"""Também podemos excluir os homens de maneira semelhante:
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}]
""" Ordenação por atributos sex em ordem crescente:
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}]
""" Ordenação por atributos sex em ordem decrescente:
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}]
"""Pedidos por vários atributos:
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}]
"""Se alguns atributos que valem a pena filtrar e pedidos não estiverem disponíveis manualmente, podemos calculá -los em tempo real:
# 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}]
"""Cada consulta de método está retornando cópia. Onde a iteração sobre os recém -criados não afeta as fontes de objetos.
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}]
""" Mas, às vezes, avaliar algumas consultas no meio da cadeia pode quebrá -la; portanto, quando você deseja explicitamente salvar um lugar de cópia da consulta e certifique -se de que outras ações no root não afetem na consulta, você pode fazer:
root_query = ObjectQuery ( humans )
copy = root_query . all ()Você também pode reverter a consulta, mas lembre -se de que ela avaliará a consulta:
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}]
""" Bitwise ou combina duas consultas. O mesmo que o método union . Observe que, depois de orar duas consultas ou até mais, pode ser necessário fazer pedidos:
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}]
""" O projeto suporta muitos comparadores que podem ser escolhidos como pós -fix para pesquisa:
eqeq faz a == bexact faz a == bin faz a in bcontains faz b in agt faz a > bgte faz a >= bge faz a >= blt faz a < blte faz a <= ble faz a <= b asc() e desc() que funcionam iguais ao order_by() , mas com a ordem especificada com antecedência.unique_justseen() e unique_everseen() para remover duplicatas. Comparação realizada por atributos passados ou delegados aos objetos Igualdade __eq__ .intersection() para encontrar objetos comuns em duas consultas. Comparação realizada por atributos passados ou delegados aos objetos Igualdade __eq__ .__len__ e __getitem__ para avaliar a consulta apenas uma vez por ciclo de vida. Qualquer forma de contribuição é apreciada. Encontrar problemas, novas idéias, novos recursos. E é claro que você pode criar relações públicas para este projeto.