การใช้งานแบบสอบถามที่ขี้เกียจประเมินสำหรับการค้นหาผ่านวัตถุ Python ที่ได้รับแรงบันดาลใจจาก Django QuerySets
ในช่วงเวลาหลายช่วงเวลาของงานการเขียนโปรแกรมของเราเราต้องกรอง iterables ในการค้นหาวัตถุที่ถูกต้องตามลำดับที่ถูกต้อง ฉันรู้ว่ารหัสเวลาส่วนใหญ่มีลักษณะเหมือนกันเกือบเหมือนกัน แต่อินเทอร์เฟซแบบไหนจะใช้ง่ายที่สุด? ในช่วงเวลานั้นฉันพบว่าการใช้งาน Django Querysets นั้นมีประโยชน์และเป็นที่รู้จักกันดี
ดังนั้นฉันจึงตัดสินใจเขียนเอ็นจิ้นแบบสอบถามขนาดเล็กที่อินเทอร์เฟซจะคล้ายกับ Django One แต่มันจะใช้ได้กับวัตถุงูหลาม ข้อสันนิษฐานเพิ่มเติมคือว่ามันจะได้รับการประเมินขี้เกียจเพื่อหลีกเลี่ยงการใช้หน่วยความจำ
ความคิดทั้งหมดถ่ายทอดในรูปแบบการตั้งชื่ออาร์กิวเมนต์คำหลัก ลองพิจารณาติดตาม qualname attr1.attr2 ซึ่งเราสามารถใช้เพื่อรับหรือตั้งค่าสำหรับแอตทริบิวต์ เครื่องยนต์นี้ทำสิ่งต่าง ๆ ในทำนองเดียวกัน แต่แทนที่จะแยกด้วย dot ( . ) เราแยกกันด้วยสัญญาณ __ ดังนั้นตัวอย่างข้างต้นสามารถแปลงเป็นชื่ออาร์กิวเมนต์คำหลักเช่น 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]
"""การเริ่มต้นวัตถุอื่น ๆ ที่ใช้ตัววนซ้ำ/iterables (มันยังคงเป็นกลไกเดียวกันเกือบเหมือนการวนซ้ำปกติ):
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}]
""" Bitwise หรือรวมสองคิวรีเข้าด้วยกัน เช่นเดียวกับวิธี 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}]
""" โครงการรองรับตัวเปรียบเทียบจำนวนมากที่สามารถเลือกเป็น postfix สำหรับการค้นหา:
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 สำหรับโครงการนี้ได้