पोनी (ORM) अपनी चाल कैसे चलता है?


111

पोनी ओआरएम जनरेटर अभिव्यक्ति को एसक्यूएल में बदलने की अच्छी चाल है। उदाहरण:

>>> select(p for p in Person if p.name.startswith('Paul'))
        .order_by(Person.name)[:2]

SELECT "p"."id", "p"."name", "p"."age"
FROM "Person" "p"
WHERE "p"."name" LIKE "Paul%"
ORDER BY "p"."name"
LIMIT 2

[Person[3], Person[1]]
>>>

मुझे पता है कि पायथन में अद्भुत आत्मनिरीक्षण और मेटाप्रोग्रामिंग बिलिन है, लेकिन यह लाइब्रेरी बिना पूर्वप्रक्रिया के जनरेटर अभिव्यक्ति का अनुवाद करने में कैसे सक्षम है? यह जादू जैसा दिखता है।

[अपडेट करें]

ब्लेंडर ने लिखा:

यहाँ वह फ़ाइल है जो आप के बाद की है। यह कुछ आत्मनिरीक्षण विज़ार्ड का उपयोग करके जनरेटर को फिर से संगठित करना लगता है। मुझे यकीन नहीं है कि यह पायथन के सिंटैक्स का 100% समर्थन करता है, लेकिन यह बहुत अच्छा है। - ब्लेंडर

मैं सोच रहा था कि वे जनरेटर अभिव्यक्ति प्रोटोकॉल से कुछ सुविधा तलाश रहे थे, लेकिन इस फ़ाइल को देख रहे थे, और astइसमें शामिल मॉड्यूल को देख रहे थे ... नहीं, वे मक्खी पर कार्यक्रम स्रोत का निरीक्षण नहीं कर रहे हैं, क्या वे हैं? दिमाग उड़ा...

@BrenBarn: अगर मैं selectफ़ंक्शन कॉल के बाहर जनरेटर को कॉल करने का प्रयास करता हूं , तो परिणाम है:

>>> x = (p for p in Person if p.age > 20)
>>> x.next()
Traceback (most recent call last):
  File "<interactive input>", line 1, in <module>
  File "<interactive input>", line 1, in <genexpr>
  File "C:\Python27\lib\site-packages\pony\orm\core.py", line 1822, in next
    % self.entity.__name__)
  File "C:\Python27\lib\site-packages\pony\utils.py", line 92, in throw
    raise exc
TypeError: Use select(...) function or Person.select(...) method for iteration
>>>

ऐसा लगता है कि वे selectफंक्शन कॉल का निरीक्षण करने और मक्खी पर पायथन अमूर्त सिंटैक्स व्याकरण पेड़ को संसाधित करने जैसे अधिक रहस्यमय तरीके से कर रहे हैं।

मैं अभी भी किसी को यह समझाते हुए देखना चाहूंगा कि स्रोत मेरे विजार्ड स्तर से परे है।


संभवत: pवस्तु टट्टू द्वारा कार्यान्वित एक प्रकार की वस्तु है जो यह देखती है कि इस पर कौन सी विधियाँ / गुण प्राप्त हो रहे हैं (जैसे name, startswith) और उन्हें SQL में रूपांतरित करता है।
ब्रेनबार

3
यहाँ वह फ़ाइल है जो आप के बाद की है। यह कुछ आत्मनिरीक्षण विज़ार्ड का उपयोग करके जनरेटर को फिर से संगठित करना लगता है। मुझे यकीन नहीं है कि यह पायथन के सिंटैक्स का 100% समर्थन करता है, लेकिन यह बहुत अच्छा है।
ब्लेंडर

1
@ ब्लेंडर: मैंने LISP में इस तरह की चाल देखी है - पायथन में इस स्टंट को खींचना सिर्फ सादा बीमारी है!
पाउलो स्कर्डीने

जवाबों:


209

टट्टू ORM लेखक यहाँ है।

पोनी ने तीन चरणों में पायथन जनरेटर को SQL क्वेरी में अनुवादित किया:

  1. जेनरेटर बायटेकोड का विघटन और जनरेटर का पुनर्निर्माण AST (अमूर्त सिंटैक्स ट्री)
  2. "सार एसक्यूएल" में पायथन एएसटी का अनुवाद - एसक्यूएल क्वेरी का सार्वभौमिक सूची-आधारित प्रतिनिधित्व
  3. विशिष्ट डेटाबेस पर निर्भर SQL बोली में अमूर्त SQL प्रतिनिधित्व परिवर्तित

सबसे जटिल हिस्सा दूसरा चरण है, जहां पोनी को पायथन के भावों के "अर्थ" को समझना चाहिए। लगता है कि आप पहले चरण में सबसे अधिक रुचि रखते हैं, इसलिए मुझे समझाएं कि विघटन कैसे काम करता है।

आइए इस प्रश्न पर विचार करें:

>>> from pony.orm.examples.estore import *
>>> select(c for c in Customer if c.country == 'USA').show()

जिसे निम्नलिखित एसक्यूएल में अनुवादित किया जाएगा:

SELECT "c"."id", "c"."email", "c"."password", "c"."name", "c"."country", "c"."address"
FROM "Customer" "c"
WHERE "c"."country" = 'USA'

और नीचे इस प्रश्न का परिणाम है जिसे प्रिंट किया जाएगा:

id|email              |password|name          |country|address  
--+-------------------+--------+--------------+-------+---------
1 |john@example.com   |***     |John Smith    |USA    |address 1
2 |matthew@example.com|***     |Matthew Reed  |USA    |address 2
4 |rebecca@example.com|***     |Rebecca Lawson|USA    |address 4

select()समारोह तर्क के रूप में एक अजगर जनरेटर को स्वीकार करता है, और उसके बाद अपने बाईटकोड विश्लेषण करती है। हम मानक अजगर disमॉड्यूल का उपयोग करके इस जनरेटर के बायोटेक निर्देश प्राप्त कर सकते हैं :

>>> gen = (c for c in Customer if c.country == 'USA')
>>> import dis
>>> dis.dis(gen.gi_frame.f_code)
  1           0 LOAD_FAST                0 (.0)
        >>    3 FOR_ITER                26 (to 32)
              6 STORE_FAST               1 (c)
              9 LOAD_FAST                1 (c)
             12 LOAD_ATTR                0 (country)
             15 LOAD_CONST               0 ('USA')
             18 COMPARE_OP               2 (==)
             21 POP_JUMP_IF_FALSE        3
             24 LOAD_FAST                1 (c)
             27 YIELD_VALUE         
             28 POP_TOP             
             29 JUMP_ABSOLUTE            3
        >>   32 LOAD_CONST               1 (None)
             35 RETURN_VALUE

पोनी ओआरएम में decompile()मॉड्यूल के भीतर फ़ंक्शन होता है pony.orm.decompilingजो एक एएसटी को बायटेकोड से पुनर्स्थापित कर सकता है:

>>> from pony.orm.decompiling import decompile
>>> ast, external_names = decompile(gen)

यहाँ, हम एएसटी नोड्स के पाठ को देख सकते हैं:

>>> ast
GenExpr(GenExprInner(Name('c'), [GenExprFor(AssName('c', 'OP_ASSIGN'), Name('.0'),
[GenExprIf(Compare(Getattr(Name('c'), 'country'), [('==', Const('USA'))]))])]))

आइए अब देखें कि decompile()फ़ंक्शन कैसे काम करता है।

decompile()समारोह एक बनाता है Decompilerवस्तु है, जो आगंतुक पैटर्न लागू करता है। डिकंपाइलर का उदाहरण एक-एक करके बायटेकोड निर्देश प्राप्त करता है। प्रत्येक निर्देश के लिए अपघटनकर्ता वस्तु अपनी विधि कहती है। इस पद्धति का नाम वर्तमान बाइटकोड निर्देश के नाम के बराबर है।

जब पायथन एक अभिव्यक्ति की गणना करता है, तो यह स्टैक का उपयोग करता है, जो गणना का एक मध्यवर्ती परिणाम संग्रहीत करता है। डिकंपाइलर ऑब्जेक्ट का भी अपना स्टैक होता है, लेकिन यह स्टैक अभिव्यक्ति गणना का परिणाम नहीं है, लेकिन अभिव्यक्ति के लिए एएसटी नोड।

जब अगले बाइटकोड निर्देश के लिए डिकंपाइलर विधि कहा जाता है, तो स्टैक से एएसटी नोड्स लेता है, उन्हें एक नए एएसटी नोड में जोड़ता है, और फिर इस नोड को स्टैक के शीर्ष पर रखता है।

उदाहरण के लिए, आइए देखें कि उपसंचाई c.country == 'USA'की गणना कैसे की जाती है। इसी बायोटेक टुकड़ा है:

              9 LOAD_FAST                1 (c)
             12 LOAD_ATTR                0 (country)
             15 LOAD_CONST               0 ('USA')
             18 COMPARE_OP               2 (==)

तो, decompiler ऑब्जेक्ट निम्न करता है:

  1. कहता है decompiler.LOAD_FAST('c')। यह विधि Name('c')नोड को डिकंपाइलर स्टैक के शीर्ष पर रखती है ।
  2. कहता है decompiler.LOAD_ATTR('country')। यह विधि Name('c')नोड को स्टैक से लेती है, नोड बनाती है Geattr(Name('c'), 'country')और इसे स्टैक के शीर्ष पर रखती है।
  3. कहता है decompiler.LOAD_CONST('USA')। यह विधि Const('USA')नोड को ढेर के ऊपर रखती है ।
  4. कहता है decompiler.COMPARE_OP('==')। यह विधि स्टैक से दो नोड्स (गेटैट्र और कॉन्स्ट) लेती है, और फिर स्टैक Compare(Getattr(Name('c'), 'country'), [('==', Const('USA'))]) के शीर्ष पर रखती है ।

सभी बायोटेकोड निर्देशों के संसाधित होने के बाद, डिकम्पॉइलर स्टैक में एक एकल एएसटी नोड होता है जो पूरे जनरेटर अभिव्यक्ति से मेल खाता है।

चूंकि पोनी ओआरएम को केवल जनरेटर और लैम्ब्डा को विघटित करने की आवश्यकता होती है, इसलिए यह उतना जटिल नहीं है, क्योंकि एक जनरेटर के लिए निर्देश प्रवाह अपेक्षाकृत सीधा है - यह सिर्फ नेस्टेड छोरों का एक गुच्छा है।

वर्तमान में पोनी ओआरएम में दो चीजों को छोड़कर पूरे जनरेटर निर्देशों को शामिल किया गया है:

  1. इनलाइन यदि भाव: a if b else c
  2. यौगिक तुलना: a < b < c

यदि पोनी इस तरह की अभिव्यक्ति का सामना करता है तो यह NotImplementedErrorअपवाद को जन्म देता है। लेकिन इस मामले में भी आप इसे स्ट्रिंग के रूप में जनरेटर की अभिव्यक्ति को पारित करके काम कर सकते हैं। जब आप एक जनरेटर को एक स्ट्रिंग के रूप में पास करते हैं तो पोनी डिकम्पाइलर मॉड्यूल का उपयोग नहीं करता है। इसके बजाय यह मानक पायथन compiler.parseफ़ंक्शन का उपयोग करके एएसटी प्राप्त करता है।

उम्मीद है कि यह आपके प्रश्न का उत्तर देगा।


26
बहुत अच्छा प्रदर्शन करने वाला: (1) बायोटेक डिकोडिंग बहुत तेज है। (2) चूंकि प्रत्येक क्वेरी में संबंधित कोड ऑब्जेक्ट होता है, इसलिए इस कोड ऑब्जेक्ट का उपयोग कैश कुंजी के रूप में किया जा सकता है। इसके कारण, Pony ORM प्रत्येक क्वेरी का केवल एक बार अनुवाद करता है, जबकि Django और SQLAlchemy को एक ही क्वेरी को बार-बार अनुवाद करना पड़ता है। (3) जैसे Pony ORM IdentityMap पैटर्न का उपयोग करता है, यह उसी लेनदेन में क्वेरी परिणामों को कैश करता है। एक पोस्ट (रूसी में) है जहां लेखक कहता है कि पोनी ओआरएम, जिओगो और एसक्यूएलकेमी की तुलना में 1.5-3 गुना तेज है, यहां तक ​​कि क्वेरी परिणाम कैशिंग के बिना भी: habrahabr.ru/post/188842
अलेक्जेंडर कोइलोव्स्की

3
यह pypy JIT कंपाइलर के साथ संगत है?
मेज़ल

2
मैंने इसका परीक्षण नहीं किया है, लेकिन कुछ Reddit टिप्पणीकर्ता का कहना है कि यह संगत है: tinyurl.com/ponyorm-pypy
अलेक्जेंडर कोज़लोवस्की

9
SQLAlchemy में क्वेरी कैशिंग है और ORM इस सुविधा का व्यापक उपयोग करता है। यह डिफ़ॉल्ट रूप से नहीं है, क्योंकि इसका वास्तविक हमारे पास SQL ​​अभिव्यक्ति के निर्माण को स्रोत कोड में स्थिति से जोड़ने के लिए एक विशेषता नहीं है, जिसे यह घोषित किया जाता है, जो कि कोड ऑब्जेक्ट वास्तव में आपको दे रहा है। हम एक ही परिणाम प्राप्त करने के लिए स्टैक फ्रेम निरीक्षण का उपयोग कर सकते हैं, लेकिन यह मेरे स्वाद के लिए थोड़ा बहुत हैकी है। एसक्यूएल की पीढ़ी किसी भी मामले में सबसे कम महत्वपूर्ण प्रदर्शन क्षेत्र है; पंक्तियाँ और बहीखाता परिवर्तन लाना है।
zzzeek

2
@ randomsurfer_123 शायद नहीं, हमें इसे लागू करने के लिए कुछ समय चाहिए (शायद एक सप्ताह), और ऐसे अन्य कार्य हैं जो हमारे लिए अधिक महत्वपूर्ण हैं।
अलेक्जेंडर कोज़लोवस्की
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.