आपको यह उपयोगी लग सकता है - पायथन इंटर्न: पायथन के लिए एक नया बयान जोड़ना , यहाँ उद्धृत:
यह लेख बेहतर ढंग से समझने का प्रयास है कि पायथन का फ्रंट-एंड कैसे काम करता है। बस दस्तावेज़ीकरण और स्रोत कोड पढ़ना थोड़ा उबाऊ हो सकता है, इसलिए मैं यहां एक हाथ-दृष्टिकोण ले रहा हूं: मैं एक जोड़ने जा रहा हूंuntil
पायथन में बयान ।
इस लेख के लिए सभी कोडिंग Python Mercurial repository दर्पण में अत्याधुनिक Py3k शाखा के खिलाफ की गई थी ।
until
कथन
रूबी जैसी कुछ भाषाओं में एक until
कथन है, जो पूरक while
( until num == 0
के बराबर while num != 0
) है। रूबी में, मैं लिख सकता हूं:
num = 3
until num == 0 do
puts num
num -= 1
end
और यह प्रिंट होगा:
3
2
1
इसलिए, मैं पायथन में एक समान क्षमता जोड़ना चाहता हूं। यह है, लिखने में सक्षम होने के नाते:
num = 3
until num == 0:
print(num)
num -= 1
एक भाषा-वकालत विषयांतर
यह लेख until
पाइथन के कथन को जोड़ने का सुझाव देने का प्रयास नहीं करता है । हालांकि मुझे लगता है कि इस तरह के बयान से कुछ कोड स्पष्ट हो जाएंगे, और यह लेख दिखाता है कि इसे जोड़ना कितना आसान है, मैं पायथन के अतिसूक्ष्मवाद के दर्शन का पूरा सम्मान करता हूं। मैं यहाँ सब करने की कोशिश कर रहा हूँ, वास्तव में, पायथन के आंतरिक कामकाज में कुछ अंतर्दृष्टि प्राप्त कर रहा है।
व्याकरण को संशोधित करना
पायथन एक कस्टम पार्सर जनरेटर का उपयोग करता है जिसका नाम है pgen
। यह एक LL (1) पार्सर है जो पाइथन स्रोत कोड को पार्स ट्री में परिवर्तित करता है। पार्सर जनरेटर का इनपुट फ़ाइल Grammar/Grammar
[1] है । यह एक साधारण पाठ फ़ाइल है जो पायथन के व्याकरण को निर्दिष्ट करती है।
[1] : यहाँ से, पायथन स्रोत की फाइलों के संदर्भ स्रोत के पेड़ की जड़ को अपेक्षाकृत दिए गए हैं, जो कि वह निर्देशिका है जहाँ आप कॉन्फ़िगर करते हैं और पायथन बनाने के लिए बनाते हैं।
व्याकरण फ़ाइल में दो संशोधन करने होंगे। पहला until
कथन के लिए एक परिभाषा जोड़ना है । मैंने पाया कि जहां while
कथन परिभाषित किया गया था ( while_stmt
), और until_stmt
नीचे जोड़ा गया [2] :
compound_stmt: if_stmt | while_stmt | until_stmt | for_stmt | try_stmt | with_stmt | funcdef | classdef | decorated
if_stmt: 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite]
while_stmt: 'while' test ':' suite ['else' ':' suite]
until_stmt: 'until' test ':' suite
[२] : यह एक सामान्य तकनीक को प्रदर्शित करता है जिसका उपयोग मैं उस स्रोत कोड को संशोधित करता हूं जिससे मैं परिचित नहीं हूं: समानता से काम करें । यह सिद्धांत आपकी सभी समस्याओं को हल नहीं करेगा, लेकिन यह निश्चित रूप से प्रक्रिया को आसान कर सकता है। चूँकि इसके लिए जो कुछ करना पड़ता है वह while
भी करना पड़ता है until
, यह एक बहुत अच्छी दिशानिर्देश के रूप में कार्य करता है।
ध्यान दें कि मैंने else
खंड को अपनी परिभाषा से बाहर करने का फैसला किया है until
, बस इसे थोड़ा अलग करने के लिए (और क्योंकि स्पष्ट रूप से मुझे नापसंद हैelse
छोरों खंड को और ऐसा नहीं लगता कि यह पायथन के ज़ेन के साथ अच्छी तरह से फिट बैठता है)।
दूसरा बदलाव , compound_stmt
शामिल करने के लिए नियम को संशोधित करना है until_stmt
, जैसा कि आप ऊपर स्निपेट में देख सकते हैं। यह सही है while_stmt
, फिर से।
आप समाप्त हो जाता है make
संशोधित करने के बाद Grammar/Grammar
, सूचना है कि pgen
कार्यक्रम के लिए चलाया जाता है फिर से उत्पन्न Include/graminit.h
और Python/graminit.c
, और उसके बाद कई फ़ाइलों को फिर से संकलित मिलता है।
एएसटी पीढ़ी कोड को संशोधित करना
पायथन पार्सर के बाद एक पार्स ट्री बनाया गया है, इस पेड़ को एएसटी में बदल दिया गया है, क्योंकि एएसटी संकलन प्रक्रिया के बाद के चरणों में काम करने के लिए बहुत सरल हैं ।
इसलिए, हम Parser/Python.asdl
उस यात्रा पर जा रहे हैं जो पायथन के एएसटी की संरचना को परिभाषित करती है और हमारे नए until
विवरण के लिए एएसटी नोड को फिर से नीचे जोड़ती है while
:
| While(expr test, stmt* body, stmt* orelse)
| Until(expr test, stmt* body)
यदि आप अब चलाते हैं make
, तो ध्यान दें कि फ़ाइलों का एक गुच्छा संकलित करने से पहले, Parser/asdl_c.py
एएसटी परिभाषा फ़ाइल से सी कोड उत्पन्न करने के लिए चलाया जाता है। यह (जैसे Grammar/Grammar
) मिनी-भाषा (दूसरे शब्दों में, एक डीएसएल) का उपयोग करके प्रोग्रामिंग को आसान बनाने के लिए पायथन सोर्स-कोड का एक और उदाहरण है। यह भी ध्यान दें कि चूंकि Parser/asdl_c.py
पायथन लिपि है, यह एक प्रकार का बूटस्ट्रैपिंग है - स्क्रैच से पाइथन बनाने के लिए, पाइथन पहले से ही उपलब्ध होना चाहिए।
जबकि Parser/asdl_c.py
(फाइलों में हमारे नए परिभाषित एएसटी नोड प्रबंधन करने के लिए कोड उत्पन्न Include/Python-ast.h
और Python/Python-ast.c
), हम अभी भी कोड लिखने के लिए है कि धर्मान्तरित इसे में एक प्रासंगिक पार्स पेड़ नोड हाथ से। यह फ़ाइल में किया जाता है Python/ast.c
। वहां, एक फ़ंक्शन नाम ast_for_stmt
पार्स ट्री नोड्स को एएसटी नोड्स में बयान के लिए परिवर्तित करता है। फिर से, हमारे पुराने मित्र द्वारा निर्देशित while
, हम switch
यौगिक कथनों को संभालने के लिए बड़े में दाएं कूदते हैं और इसके लिए एक खंड जोड़ते हैं until_stmt
:
case while_stmt:
return ast_for_while_stmt(c, ch);
case until_stmt:
return ast_for_until_stmt(c, ch);
अब हमें लागू करना चाहिए ast_for_until_stmt
। यह रहा:
static stmt_ty
ast_for_until_stmt(struct compiling *c, const node *n)
{
/* until_stmt: 'until' test ':' suite */
REQ(n, until_stmt);
if (NCH(n) == 4) {
expr_ty expression;
asdl_seq *suite_seq;
expression = ast_for_expr(c, CHILD(n, 1));
if (!expression)
return NULL;
suite_seq = ast_for_suite(c, CHILD(n, 3));
if (!suite_seq)
return NULL;
return Until(expression, suite_seq, LINENO(n), n->n_col_offset, c->c_arena);
}
PyErr_Format(PyExc_SystemError,
"wrong number of tokens for 'until' statement: %d",
NCH(n));
return NULL;
}
फिर से, इसे बराबर देखते हुए कोड किया गया था, इस ast_for_while_stmt
अंतर के साथ कि until
मैंने निर्णय लिया है कि मैं else
खंड का समर्थन नहीं करता । जैसा कि अपेक्षित था, एएसटी को पुन: निर्मित किया जाता है, ast_for_expr
स्थिति की अभिव्यक्ति के लिए और बयान ast_for_suite
के शरीर के लिए अन्य एएसटी कार्यों का उपयोग करके until
। अंत में, नाम दिया गया एक नया नोड Until
वापस आ गया है।
ध्यान दें कि हम n
कुछ मैक्रोज़ जैसे NCH
और का उपयोग करके पार्स-ट्री नोड का उपयोग करते हैं CHILD
। ये समझने लायक हैं - इनका कोड है Include/node.h
।
पाचन: एएसटी रचना
मैंने until
बयान के लिए एक नए प्रकार का एएसटी बनाने का विकल्प चुना , लेकिन वास्तव में यह आवश्यक नहीं है। मैं कुछ काम बचा सकता था और मौजूदा एएसटी नोड्स की संरचना का उपयोग करके नई कार्यक्षमता को लागू कर सकता था:
until condition:
# do stuff
कार्यात्मक रूप से इसके बराबर है:
while not condition:
# do stuff
में Until
नोड बनाने के बजाय ast_for_until_stmt
, मैं Not
एक While
नोड को एक बच्चे के रूप में बना सकता था। चूंकि एएसटी कंपाइलर पहले से ही जानता है कि इन नोड्स को कैसे संभालना है, प्रक्रिया के अगले चरणों को छोड़ दिया जा सकता है।
एएसटी को बायटेकोड में संकलित करना
अगला चरण Python bytecode में AST को संकलित कर रहा है। संकलन का एक मध्यवर्ती परिणाम है जो एक सीएफजी (नियंत्रण प्रवाह ग्राफ़) है, लेकिन चूंकि एक ही कोड इसे संभालता है इसलिए मैं इस विवरण को अभी के लिए अनदेखा कर दूंगा और इसे किसी अन्य लेख के लिए छोड़ दूंगा।
अगला कोड हम देखेंगे Python/compile.c
। के नेतृत्व के बाद while
, हम फ़ंक्शन compiler_visit_stmt
को खोजते हैं, जो कि बाइटकोड में बयान संकलन के लिए जिम्मेदार है। हम इसके लिए एक खंड जोड़ते हैं Until
:
case While_kind:
return compiler_while(c, s);
case Until_kind:
return compiler_until(c, s);
यदि आप आश्चर्य करते हैं कि क्या Until_kind
है, तो यह _stmt_kind
एएसटी परिभाषा फ़ाइल से स्वचालित रूप से उत्पन्न एक स्थिर (वास्तव में गणना का मूल्य ) है Include/Python-ast.h
। वैसे भी, हम कहते हैं compiler_until
, जो अभी भी मौजूद नहीं है। मैं इसे एक पल के लिए मिल जाएगा।
यदि आप मेरी तरह उत्सुक हैं, तो आप ध्यान देंगे कि compiler_visit_stmt
अजीब है। grep
स्रोत वृक्ष को काटे जाने की कोई मात्रा नहीं बताती है कि इसे कहां कहा जाता है। जब यह मामला होता है, तो केवल एक विकल्प बचता है - सी मैक्रो-फू। वास्तव में, एक छोटी जांच हमें VISIT
परिभाषित मैक्रो की ओर ले जाती है Python/compile.c
:
#define VISIT(C, TYPE, V) {\
if (!compiler_visit_ ## TYPE((C), (V))) \
return 0; \
यह आह्वान करने के लिए प्रयोग किया जाता है compiler_visit_stmt
में compiler_body
। हमारे व्यवसाय पर वापस, हालांकि ...
जैसा कि वादा किया गया है, यहाँ है compiler_until
:
static int
compiler_until(struct compiler *c, stmt_ty s)
{
basicblock *loop, *end, *anchor = NULL;
int constant = expr_constant(s->v.Until.test);
if (constant == 1) {
return 1;
}
loop = compiler_new_block(c);
end = compiler_new_block(c);
if (constant == -1) {
anchor = compiler_new_block(c);
if (anchor == NULL)
return 0;
}
if (loop == NULL || end == NULL)
return 0;
ADDOP_JREL(c, SETUP_LOOP, end);
compiler_use_next_block(c, loop);
if (!compiler_push_fblock(c, LOOP, loop))
return 0;
if (constant == -1) {
VISIT(c, expr, s->v.Until.test);
ADDOP_JABS(c, POP_JUMP_IF_TRUE, anchor);
}
VISIT_SEQ(c, stmt, s->v.Until.body);
ADDOP_JABS(c, JUMP_ABSOLUTE, loop);
if (constant == -1) {
compiler_use_next_block(c, anchor);
ADDOP(c, POP_BLOCK);
}
compiler_pop_fblock(c, LOOP, loop);
compiler_use_next_block(c, end);
return 1;
}
मेरे पास बनाने के लिए एक स्वीकारोक्ति है: यह कोड पायथन बाइटकोड की गहरी समझ के आधार पर नहीं लिखा गया था। बाकी लेख की तरह, यह परिजनों के compiler_while
कार्य के अनुकरण में किया गया था । हालाँकि, इसे ध्यान से पढ़ते हुए, यह ध्यान में रखते हुए कि पायथन वीएम स्टैक-आधारित है, और dis
मॉड्यूल के प्रलेखन में झलक रहा है , जिसमें विवरण के साथ पायथन बायोटेक्कोड की सूची है , यह समझना संभव है कि क्या हो रहा है।
यही है, हम कर रहे हैं ... हम नहीं हैं?
सभी परिवर्तन करने और चलाने के बाद make
, हम नए संकलित अजगर को चला सकते हैं और हमारे नए until
कथन को आजमा सकते हैं :
>>> until num == 0:
... print(num)
... num -= 1
...
3
2
1
वोइला, यह काम करता है! आइए देखें कि नए स्टेटमेंट के लिए dis
मॉड्यूल द्वारा निम्न प्रकार से बनाया गया बायटेकोड देखें :
import dis
def myfoo(num):
until num == 0:
print(num)
num -= 1
dis.dis(myfoo)
यहाँ परिणाम है:
4 0 SETUP_LOOP 36 (to 39)
>> 3 LOAD_FAST 0 (num)
6 LOAD_CONST 1 (0)
9 COMPARE_OP 2 (==)
12 POP_JUMP_IF_TRUE 38
5 15 LOAD_NAME 0 (print)
18 LOAD_FAST 0 (num)
21 CALL_FUNCTION 1
24 POP_TOP
6 25 LOAD_FAST 0 (num)
28 LOAD_CONST 2 (1)
31 INPLACE_SUBTRACT
32 STORE_FAST 0 (num)
35 JUMP_ABSOLUTE 3
>> 38 POP_BLOCK
>> 39 LOAD_CONST 0 (None)
42 RETURN_VALUE
सबसे दिलचस्प ऑपरेशन 12 नंबर है: यदि स्थिति सच है, तो हम लूप के बाद कूदते हैं। यह सही शब्दार्थ है until
। यदि कूद को अंजाम नहीं दिया जाता है, तो लूप बॉडी तब तक चलती रहती है, जब तक वह ऑपरेशन 35 की स्थिति में वापस कूद जाती है।
अपने परिवर्तन के बारे में अच्छा महसूस करते हुए, मैंने इसके बाद myfoo(3)
अपने बायोटेक को दिखाने के बजाय फ़ंक्शन (निष्पादित ) को चलाने की कोशिश की । परिणाम उत्साहजनक से कम था:
Traceback (most recent call last):
File "zy.py", line 9, in
myfoo(3)
File "zy.py", line 5, in myfoo
print(num)
SystemError: no locals when loading 'print'
वाह ... यह अच्छा नहीं हो सकता। तो क्या गलत हुआ?
लापता प्रतीक तालिका का मामला
जब पायल कंपाइलर एक कदम रखता है तो एएसटी का संकलन उस कोड के लिए प्रतीक तालिका बनाता है। करने के लिए कॉल PySymtable_Build
में PyAST_Compile
प्रतीक तालिका मॉड्यूल (में कॉल Python/symtable.c
) है, जो एक तरह से कोड पीढ़ी कार्यों के लिए इसी तरह के एएसटी चलता है। प्रत्येक स्कोप के लिए एक प्रतीक तालिका होने से कंपाइलर को कुछ महत्वपूर्ण जानकारी का पता लगाने में मदद मिलती है, जैसे कि कौन-सी चर वैश्विक हैं और जो एक स्कोप के लिए स्थानीय हैं।
इस समस्या को ठीक करने के लिए, हम संशोधित करने के लिए है symtable_visit_stmt
में समारोह Python/symtable.c
, से निपटने के लिए कोड जोड़ने until
, बयानों के लिए इसी तरह के कोड के बाद while
बयान [3] :
case While_kind:
VISIT(st, expr, s->v.While.test);
VISIT_SEQ(st, stmt, s->v.While.body);
if (s->v.While.orelse)
VISIT_SEQ(st, stmt, s->v.While.orelse);
break;
case Until_kind:
VISIT(st, expr, s->v.Until.test);
VISIT_SEQ(st, stmt, s->v.Until.body);
break;
[३] : वैसे, इस कोड के बिना इसके लिए एक कंपाइलर चेतावनी है Python/symtable.c
। कंपाइलर ने नोटिस किया कि Until_kind
एन्यूमरेशन वैल्यू को स्विच स्टेटमेंट में हैंडल नहीं किया गया है symtable_visit_stmt
और शिकायत करता है। कंपाइलर चेतावनियों की जांच करना हमेशा महत्वपूर्ण होता है!
और अब हम वास्तव में कर रहे हैं। इस परिवर्तन के बाद स्रोत को संकलित करना myfoo(3)
अपेक्षित रूप से कार्य का निष्पादन करता है।
निष्कर्ष
इस लेख में मैंने दिखाया है कि पायथन में एक नया बयान कैसे जोड़ा जाए। यद्यपि पाइथन संकलक के कोड में काफी छेड़छाड़ की आवश्यकता होती है, परिवर्तन को लागू करना मुश्किल नहीं था, क्योंकि मैंने एक दिशानिर्देश के रूप में एक समान और मौजूदा बयान का उपयोग किया था।
पायथन कंपाइलर सॉफ्टवेयर का एक परिष्कृत हिस्सा है, और मैं इसमें विशेषज्ञ होने का दावा नहीं करता। हालाँकि, मैं वास्तव में पायथन के इंटर्न और विशेष रूप से इसके फ्रंट-एंड में दिलचस्पी रखता हूं। इसलिए, मैंने इस अभ्यास को संकलक के सिद्धांतों और स्रोत कोड के सैद्धांतिक अध्ययन के लिए एक बहुत ही उपयोगी साथी पाया। यह भविष्य के लेखों के लिए एक आधार के रूप में काम करेगा जो संकलक में गहरा हो जाएगा।
संदर्भ
मैंने इस लेख के निर्माण के लिए कुछ उत्कृष्ट संदर्भों का उपयोग किया। यहां वे किसी खास क्रम में नहीं हैं:
- PEP 339: CPython संकलक का डिज़ाइन - शायद पायथन संकलक के लिए आधिकारिक प्रलेखन का सबसे महत्वपूर्ण और व्यापक टुकड़ा है । बहुत छोटा होने के कारण, यह दर्द से पाइथन के इंटर्नल के अच्छे प्रलेखन की कमी को प्रदर्शित करता है।
- "पायथन कंपाइलर इंटर्नल्स" - थॉमस ली का एक लेख
- "पायथन: डिज़ाइन एंड इम्प्लीमेंटेशन" - गुइडो वैन रोसुम की एक प्रस्तुति
- पाइथन (2.5) वर्चुअल मशीन, ए गाइडेड टूर - पीटर ट्रेजर द्वारा एक प्रस्तुति
मूल स्रोत