คุณสามารถเพิ่มงบใหม่ (เช่นprint
, raise
, with
) ไวยากรณ์งูใหญ่?
บอกว่ายอม ..
mystatement "Something"
หรือ,
new_if True:
print "example"
ไม่มากถ้าคุณควรแต่ถ้าเป็นไปได้ (ย่อมาจากการแก้ไขโค้ดล่าม python)
คุณสามารถเพิ่มงบใหม่ (เช่นprint
, raise
, with
) ไวยากรณ์งูใหญ่?
บอกว่ายอม ..
mystatement "Something"
หรือ,
new_if True:
print "example"
ไม่มากถ้าคุณควรแต่ถ้าเป็นไปได้ (ย่อมาจากการแก้ไขโค้ดล่าม python)
คำตอบ:
คุณอาจพบว่าสิ่งนี้มีประโยชน์ - Python internals: การเพิ่มคำสั่งใหม่ให้กับ Pythonโดยอ้างถึงที่นี่:
บทความนี้เป็นความพยายามที่จะทำความเข้าใจให้ดีขึ้นว่าส่วนหน้าของ Python ทำงานอย่างไร การอ่านเอกสารและซอร์สโค้ดอาจจะน่าเบื่อไปหน่อยดังนั้นฉันจึงใช้แนวทางปฏิบัติที่นี่: ฉันจะเพิ่มuntil
คำสั่งให้กับ Python
ทั้งหมดเข้ารหัสสำหรับบทความนี้ที่ได้กระทำกับการตัดขอบสาขา Py3k ในกระจกที่เก็บหลาม Mercurial
until
คำสั่งบางภาษาเช่น Ruby มีuntil
คำสั่งซึ่งเป็นส่วนเสริมของwhile
( until num == 0
เทียบเท่ากับwhile num != 0
) ใน Ruby ฉันสามารถเขียน:
num = 3
until num == 0 do
puts num
num -= 1
end
และจะพิมพ์:
3
2
1
ดังนั้นฉันต้องการเพิ่มความสามารถที่คล้ายกันให้กับ Python นั่นคือความสามารถในการเขียน:
num = 3
until num == 0:
print(num)
num -= 1
บทความนี้ไม่ได้พยายามแนะนำการเพิ่มuntil
คำสั่งใน Python แม้ว่าฉันคิดว่าคำสั่งดังกล่าวจะทำให้โค้ดบางส่วนชัดเจนขึ้นและบทความนี้แสดงให้เห็นว่าการเพิ่มนั้นง่ายเพียงใด แต่ฉันก็เคารพปรัชญาความเรียบง่ายของ Python อย่างสมบูรณ์ สิ่งที่ฉันพยายามทำที่นี่จริงๆคือได้รับข้อมูลเชิงลึกเกี่ยวกับการทำงานภายในของ Python
งูหลามใช้เครื่องกำเนิดไฟฟ้า parser pgen
ที่กำหนดเองที่มีชื่อว่า นี่คือตัวแยกวิเคราะห์ LL (1) ที่แปลงซอร์สโค้ด Python เป็นโครงสร้างแยกวิเคราะห์ อินพุตกำเนิดแยกวิเคราะห์เป็นไฟล์[1]Grammar/Grammar
นี่คือไฟล์ข้อความธรรมดาที่ระบุไวยากรณ์ของ Python
[1] : จากนี้ไปการอ้างอิงถึงไฟล์ในซอร์ส Python จะได้รับค่อนข้างตรงกับรูทของแผนผังซอร์สซึ่งเป็นไดเร็กทอรีที่คุณเรียกใช้การกำหนดค่าและสร้างเพื่อสร้าง Python
ต้องทำการแก้ไขสองอย่างกับไฟล์ไวยากรณ์ ประการแรกคือการเพิ่มคำจำกัดความสำหรับ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
[2] : นี่แสดงให้เห็นถึงเทคนิคทั่วไปผมใช้เมื่อแก้ไขรหัสที่มาฉันไม่คุ้นเคยกับการทำงานโดยความคล้ายคลึงกัน หลักการนี้ไม่สามารถแก้ปัญหาของคุณได้ทั้งหมด แต่สามารถทำให้กระบวนการนี้ง่ายขึ้น เนื่องจากทุกสิ่งที่ต้องทำwhile
ก็ต้องทำเพื่อuntil
มันจึงเป็นแนวทางที่ดีทีเดียว
โปรดทราบว่าฉันได้ตัดสินใจที่จะแยกelse
ประโยคออกจากคำจำกัดความของฉันuntil
เพียงเพื่อให้มันแตกต่างกันเล็กน้อย (และเพราะฉันไม่ชอบelse
ประโยคของลูปและไม่คิดว่ามันเข้ากันได้ดีกับ Zen of Python)
การเปลี่ยนแปลงที่สองคือการแก้ไขกฎสำหรับcompound_stmt
รวมuntil_stmt
ดังที่คุณเห็นในตัวอย่างด้านบน หลังจากwhile_stmt
นั้นอีกครั้ง
เมื่อคุณเรียกใช้make
หลังจากแก้ไขGrammar/Grammar
แจ้งให้ทราบว่าpgen
โปรแกรมจะดำเนินการอีกครั้งสร้างInclude/graminit.h
และPython/graminit.c
แล้วหลายไฟล์ได้อีกรวบรวม
หลังจากตัวแยกวิเคราะห์ Python ได้สร้างแผนผังการแยกวิเคราะห์โครงสร้างนี้จะถูกแปลงเป็น AST เนื่องจาก AST นั้นง่ายกว่ามากในการทำงานในขั้นตอนต่อ ๆ ไปของกระบวนการคอมไพล์
ดังนั้นเราจะไปเยี่ยมชมParser/Python.asdl
ซึ่งกำหนดโครงสร้างของ AST ของ Python และเพิ่มโหนด AST สำหรับuntil
คำสั่งใหม่ของเราอีกครั้งด้านล่างwhile
:
| While(expr test, stmt* body, stmt* orelse)
| Until(expr test, stmt* body)
หากคุณเรียกใช้ตอนนี้make
โปรดสังเกตว่าก่อนที่จะรวบรวมไฟล์จำนวนมากParser/asdl_c.py
ให้รันเพื่อสร้างรหัส C จากไฟล์ข้อกำหนด AST นี่ (เช่นGrammar/Grammar
) เป็นอีกตัวอย่างหนึ่งของซอร์สโค้ด Python โดยใช้มินิภาษา (หรืออีกนัยหนึ่งคือ DSL) เพื่อลดความซับซ้อนในการเขียนโปรแกรม โปรดทราบว่าเนื่องจากParser/asdl_c.py
เป็นสคริปต์ Python นี่คือการบูตแบบหนึ่ง - ในการสร้าง Python ตั้งแต่เริ่มต้น Python จะต้องพร้อมใช้งานอยู่แล้ว
ในขณะที่Parser/asdl_c.py
สร้างโค้ดเพื่อจัดการโหนด AST ที่กำหนดขึ้นใหม่ของเรา (ลงในไฟล์Include/Python-ast.h
และPython/Python-ast.c
) เรายังคงต้องเขียนโค้ดที่แปลงโหนดพาร์สทรีที่เกี่ยวข้องลงไปด้วยมือ Python/ast.c
นี้จะกระทำในแฟ้ม ที่นั่นฟังก์ชั่นที่มีชื่อว่าast_for_stmt
แปลงโหนดต้นไม้แยกวิเคราะห์สำหรับงบเป็นโหนด AST อีกครั้งตามคำแนะนำของเพื่อนเก่าของ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 ถูกสร้างขึ้นแบบวนซ้ำโดยใช้ฟังก์ชันการสร้าง AST อื่น ๆ เช่นast_for_expr
สำหรับนิพจน์เงื่อนไขและast_for_suite
สำหรับเนื้อหาของuntil
คำสั่ง ในที่สุดโหนดใหม่ที่ตั้งชื่อUntil
จะถูกส่งคืน
โปรดทราบว่าเราเข้าถึงโหนดแยกต้นไม้n
ใช้มาโครบางคนชอบและNCH
CHILD
เหล่านี้เป็นความเข้าใจคุ้มค่า - Include/node.h
รหัสของพวกเขาอยู่ใน
ฉันเลือกที่จะสร้าง AST ประเภทใหม่สำหรับuntil
คำสั่ง แต่จริงๆแล้วมันไม่จำเป็น ฉันสามารถบันทึกงานบางอย่างและใช้ฟังก์ชันใหม่โดยใช้องค์ประกอบของโหนด AST ที่มีอยู่เนื่องจาก:
until condition:
# do stuff
เทียบเท่ากับการทำงาน:
while not condition:
# do stuff
แทนที่จะสร้างUntil
โหนดในast_for_until_stmt
ฉันสามารถสร้างNot
โหนดที่มีWhile
โหนดเป็นชายด์ได้ เนื่องจากคอมไพเลอร์ AST รู้วิธีจัดการกับโหนดเหล่านี้แล้วจึงสามารถข้ามขั้นตอนต่อไปของกระบวนการได้
ขั้นตอนต่อไปคือการรวบรวม AST เป็น Python bytecode การคอมไพล์มีผลลัพธ์ระดับกลางซึ่งเป็น CFG (Control Flow Graph) แต่เนื่องจากโค้ดเดียวกันนี้จัดการได้ฉันจะไม่สนใจรายละเอียดนี้ในตอนนี้และปล่อยไว้สำหรับบทความอื่น
Python/compile.c
รหัสเราจะดูที่อยู่ถัดไป ตามการนำไปสู่while
เราจะพบฟังก์ชันcompiler_visit_stmt
ซึ่งมีหน้าที่รวบรวมคำสั่งเป็น bytecode เราเพิ่มประโยคสำหรับUntil
:
case While_kind:
return compiler_while(c, s);
case Until_kind:
return compiler_until(c, s);
หากคุณสงสัยว่าอะไรUntil_kind
คือค่าคงที่ (จริงๆแล้วคือค่าของการ_stmt_kind
แจงนับ) ที่สร้างขึ้นโดยอัตโนมัติจากไฟล์นิยาม AST เป็นไฟล์Include/Python-ast.h
. อย่างไรก็ตามเราเรียกว่าcompiler_until
ซึ่งแน่นอนว่ายังไม่มีอยู่จริง ฉันจะไปสักครู่
ถ้าคุณอยากรู้อยากเห็นเหมือนฉันคุณจะสังเกตเห็นว่าcompiler_visit_stmt
มันแปลก ไม่มีการgrep
เปิดเผยจำนวนของต้นไม้ต้นทางที่เรียกว่า ในกรณีนี้จะเหลือเพียงตัวเลือกเดียวคือ C macro-fu อันที่จริงการตรวจสอบสั้น ๆ นำเราไปสู่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;
}
ฉันมีคำสารภาพที่ต้องทำ: รหัสนี้ไม่ได้เขียนขึ้นโดยอาศัยความเข้าใจอย่างลึกซึ้งเกี่ยวกับ bytecode ของ Python เช่นเดียวกับส่วนที่เหลือของบทความก็ทำเลียนแบบcompiler_while
ฟังก์ชันเครือญาติ อย่างไรก็ตามเมื่ออ่านอย่างละเอียดโปรดทราบว่า Python VM เป็นแบบสแต็กและมองเข้าไปในเอกสารประกอบของdis
โมดูลซึ่งมีรายการไบต์โค้ดของ Pythonพร้อมคำอธิบายจึงเป็นไปได้ที่จะเข้าใจว่าเกิดอะไรขึ้น
หลังจากทำการเปลี่ยนแปลงทั้งหมดmake
แล้วเราสามารถรัน Python ที่คอมไพล์ใหม่และลองใช้until
คำสั่งใหม่ของเรา:
>>> until num == 0:
... print(num)
... num -= 1
...
3
2
1
Voila ได้ผล! มาดู bytecode ที่สร้างขึ้นสำหรับคำสั่งใหม่โดยใช้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)
) แทนการแสดง bytecode ผลลัพธ์น้อยกว่าการให้กำลังใจ:
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'
โอ้โฮ ... ไม่ดีเลย แล้วเกิดอะไรขึ้น?
หนึ่งในขั้นตอนที่คอมไพเลอร์ Python ดำเนินการเมื่อคอมไพล์ AST คือสร้างตารางสัญลักษณ์สำหรับโค้ดที่คอมไพล์ เรียกร้องให้PySymtable_Build
ในPyAST_Compile
สายเป็นโมดูลตารางสัญลักษณ์ ( Python/symtable.c
) ซึ่งเดิน AST ในลักษณะที่คล้ายคลึงกับฟังก์ชั่นการสร้างรหัสที่ การมีตารางสัญลักษณ์สำหรับแต่ละขอบเขตจะช่วยให้คอมไพลเลอร์สามารถหาข้อมูลสำคัญบางอย่างได้เช่นตัวแปรใดเป็นแบบโกลบอลและตัวแปรใดที่อยู่ในขอบเขต
ในการแก้ไขปัญหาเราต้องแก้ไข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;
[3] : Python/symtable.c
โดยวิธีการที่ไม่มีรหัสนี้มีคำเตือนสำหรับคอมไพเลอร์ คอมไพลเลอร์สังเกตว่าUntil_kind
ค่าการแจงนับไม่ได้รับการจัดการในคำสั่ง switch symtable_visit_stmt
และบ่น การตรวจสอบคำเตือนของคอมไพเลอร์เป็นสิ่งสำคัญเสมอ!
และตอนนี้เราทำเสร็จแล้ว การรวบรวมแหล่งที่มาหลังจากการเปลี่ยนแปลงนี้ทำให้การmyfoo(3)
ทำงานเป็นไปตามที่คาดไว้
ในบทความนี้ฉันได้สาธิตวิธีการเพิ่มคำสั่งใหม่ให้กับ Python แม้ว่าจะต้องมีการปรับแต่งโค้ดของคอมไพเลอร์ Python แต่การเปลี่ยนแปลงก็ไม่ยากที่จะนำไปใช้เพราะฉันใช้คำสั่งที่คล้ายกันและที่มีอยู่เป็นแนวทาง
คอมไพเลอร์ Python เป็นซอฟต์แวร์ที่ซับซ้อนและฉันไม่ได้อ้างว่าเป็นผู้เชี่ยวชาญ อย่างไรก็ตามฉันสนใจภายในของ Python จริงๆและโดยเฉพาะส่วนหน้า ดังนั้นฉันจึงพบว่าแบบฝึกหัดนี้มีประโยชน์มากสำหรับการศึกษาเชิงทฤษฎีเกี่ยวกับหลักการของคอมไพเลอร์และซอร์สโค้ด มันจะใช้เป็นฐานสำหรับบทความในอนาคตที่จะเจาะลึกลงไปในคอมไพเลอร์
ฉันใช้ข้อมูลอ้างอิงที่ยอดเยี่ยมสองสามข้อสำหรับการสร้างบทความนี้ พวกเขาอยู่ที่นี่โดยไม่เรียงลำดับเฉพาะ:
until
คือisa
/ isan
เหมือนif something isa dict:
หรือif something isan int:
วิธีหนึ่งในการทำสิ่งนี้คือการประมวลผลซอร์สล่วงหน้าและแก้ไขโดยแปลคำสั่งที่เพิ่มของคุณเป็น python วิธีนี้จะนำมาซึ่งปัญหาต่าง ๆ และฉันไม่แนะนำให้ใช้สำหรับการใช้งานทั่วไป แต่สำหรับการทดลองกับภาษาหรือการเขียนโปรแกรมแบบเฉพาะเจาะจงอาจมีประโยชน์ในบางครั้ง
ตัวอย่างเช่นสมมติว่าเราต้องการแนะนำคำสั่ง "myprint" ซึ่งแทนที่จะพิมพ์ไปที่หน้าจอแทนที่จะบันทึกไฟล์เฉพาะ เช่น:
myprint "This gets logged to file"
จะเทียบเท่ากับ
print >>open('/tmp/logfile.txt','a'), "This gets logged to file"
มีตัวเลือกมากมายในการทำการแทนที่ตั้งแต่การแทนที่ regex ไปจนถึงการสร้าง AST ไปจนถึงการเขียน parser ของคุณเองขึ้นอยู่กับว่าไวยากรณ์ของคุณใกล้เคียงกับ python ที่มีอยู่แค่ไหน แนวทางระดับกลางที่ดีคือการใช้โมดูลโทเค็นไนเซอร์ สิ่งนี้จะช่วยให้คุณสามารถเพิ่มคำหลักใหม่โครงสร้างการควบคุมและอื่น ๆ ในขณะที่ตีความแหล่งที่มาคล้ายกับตัวแปล python ดังนั้นจึงหลีกเลี่ยงไม่ให้โซลูชัน regex หยาบแตก สำหรับ "myprint" ข้างต้นคุณสามารถเขียนรหัสการแปลงต่อไปนี้:
import tokenize
LOGFILE = '/tmp/log.txt'
def translate(readline):
for type, name,_,_,_ in tokenize.generate_tokens(readline):
if type ==tokenize.NAME and name =='myprint':
yield tokenize.NAME, 'print'
yield tokenize.OP, '>>'
yield tokenize.NAME, "open"
yield tokenize.OP, "("
yield tokenize.STRING, repr(LOGFILE)
yield tokenize.OP, ","
yield tokenize.STRING, "'a'"
yield tokenize.OP, ")"
yield tokenize.OP, ","
else:
yield type,name
(สิ่งนี้ทำให้ myprint เป็นคีย์เวิร์ดอย่างมีประสิทธิภาพดังนั้นการใช้เป็นตัวแปรที่อื่นอาจทำให้เกิดปัญหาได้)
ปัญหาคือจะใช้อย่างไรเพื่อให้โค้ดของคุณใช้งานได้จาก python วิธีหนึ่งก็คือการเขียนฟังก์ชันการนำเข้าของคุณเองและใช้เพื่อโหลดโค้ดที่เขียนด้วยภาษาที่คุณกำหนดเอง เช่น:
import new
def myimport(filename):
mod = new.module(filename)
f=open(filename)
data = tokenize.untokenize(translate(f.readline))
exec data in mod.__dict__
return mod
สิ่งนี้ต้องการให้คุณจัดการโค้ดที่กำหนดเองของคุณแตกต่างจากโมดูล python ทั่วไปอย่างไรก็ตาม เช่น " some_mod = myimport("some_mod.py")
" มากกว่า " import some_mod
"
อีกวิธีหนึ่งที่ค่อนข้างเรียบร้อย (แม้ว่าจะแฮ็กกี้) คือการสร้างการเข้ารหัสแบบกำหนดเอง (ดูPEP 263 ) ตามที่สูตรนี้แสดงให้เห็น คุณสามารถใช้สิ่งนี้เป็น:
import codecs, cStringIO, encodings
from encodings import utf_8
class StreamReader(utf_8.StreamReader):
def __init__(self, *args, **kwargs):
codecs.StreamReader.__init__(self, *args, **kwargs)
data = tokenize.untokenize(translate(self.stream.readline))
self.stream = cStringIO.StringIO(data)
def search_function(s):
if s!='mylang': return None
utf8=encodings.search_function('utf8') # Assume utf8 encoding
return codecs.CodecInfo(
name='mylang',
encode = utf8.encode,
decode = utf8.decode,
incrementalencoder=utf8.incrementalencoder,
incrementaldecoder=utf8.incrementaldecoder,
streamreader=StreamReader,
streamwriter=utf8.streamwriter)
codecs.register(search_function)
หลังจากรันโค้ดนี้แล้ว (เช่นคุณสามารถวางไว้ใน. pythonrc หรือ site.py) โค้ดใด ๆ ที่ขึ้นต้นด้วยความคิดเห็น "# coding: mylang" จะถูกแปลโดยอัตโนมัติผ่านขั้นตอนก่อนการประมวลผลข้างต้น เช่น.
# coding: mylang
myprint "this gets logged to file"
for i in range(10):
myprint "so does this : ", i, "times"
myprint ("works fine" "with arbitrary" + " syntax"
"and line continuations")
คำเตือน:
มีปัญหากับวิธีการก่อนตัวประมวลผลเนื่องจากคุณอาจคุ้นเคยหากคุณเคยทำงานกับตัวประมวลผลล่วงหน้า C หลักคือการดีบัก python ทั้งหมดที่เห็นคือไฟล์ที่ประมวลผลล่วงหน้าซึ่งหมายความว่าข้อความที่พิมพ์ใน stack trace ฯลฯ จะอ้างถึงสิ่งนั้น หากคุณได้ทำการแปลจำนวนมากสิ่งนี้อาจแตกต่างจากข้อความต้นฉบับของคุณมาก ตัวอย่างด้านบนไม่เปลี่ยนหมายเลขบรรทัด ฯลฯ ดังนั้นจะไม่แตกต่างกันมากเกินไป แต่ยิ่งคุณเปลี่ยนมากเท่าไหร่ก็จะยิ่งเข้าใจยากขึ้นเท่านั้น
myimport
กับโมดูลที่มีprint 1
เพียงบรรทัดเดียวของรหัสที่ให้ผล=1 ... SyntaxError: invalid syntax
b=myimport("b.py")
" และ b.py ที่มี just " print 1
" ข้อผิดพลาดมีอะไรเพิ่มเติมหรือไม่ (stack trace ฯลฯ )?
import
ใช้ builtin __import__
ดังนั้นหากคุณเขียนทับ ( ก่อนที่จะนำเข้าโมดูลที่ต้องนำเข้าที่แก้ไข) คุณไม่จำเป็นต้องแยกต่างหากmyimport
ใช่มันเป็นไปได้ในระดับหนึ่ง มีโมดูลที่ใช้sys.settrace()
ในการติดตั้งgoto
และcomefrom
"คีย์เวิร์ด":
from goto import goto, label
for i in range(1, 10):
for j in range(1, 20):
print i, j
if j == 3:
goto .end # breaking out from nested loop
label .end
print "Finished"
ขาดการเปลี่ยนและคอมไพล์ซอร์สโค้ดใหม่ (ซึ่งเป็นไปได้ด้วยโอเพนซอร์ส) การเปลี่ยนภาษาพื้นฐานเป็นไปไม่ได้จริงๆ
แม้ว่าคุณจะทำการคอมไพล์ซอร์สใหม่ แต่ก็ไม่ได้เป็นไพ ธ อน แต่เป็นเวอร์ชันที่ถูกแฮ็กอัปที่เปลี่ยนไปซึ่งคุณต้องระวังอย่างมากที่จะไม่นำข้อบกพร่องเข้ามา
อย่างไรก็ตามฉันไม่แน่ใจว่าทำไมคุณถึงต้องการ คุณสมบัติเชิงวัตถุของ Python ทำให้การบรรลุผลลัพธ์ที่คล้ายกันกับภาษานั้นทำได้ง่ายมาก
คำตอบทั่วไป: คุณต้องประมวลผลไฟล์ต้นฉบับของคุณล่วงหน้า
คำตอบที่เฉพาะเจาะจงมากขึ้น: ติดตั้งEasyExtendและทำตามขั้นตอนต่อไปนี้
i) สร้าง langlet ใหม่ (ภาษาส่วนขยาย)
import EasyExtend
EasyExtend.new_langlet("mystmts", prompt = "my> ", source_ext = "mypy")
หากไม่มีข้อกำหนดเพิ่มเติมจะต้องสร้างไฟล์จำนวนมากภายใต้ EasyExtend / langlets / mystmts /
ii) เปิด mystmts / parsedef / Grammar.ext และเพิ่มบรรทัดต่อไปนี้
small_stmt: (expr_stmt | print_stmt | del_stmt | pass_stmt | flow_stmt |
import_stmt | global_stmt | exec_stmt | assert_stmt | my_stmt )
my_stmt: 'mystatement' expr
นี่เพียงพอที่จะกำหนดไวยากรณ์ของคำสั่งใหม่ของคุณ small_stmt ที่ไม่ใช่เทอร์มินัลเป็นส่วนหนึ่งของไวยากรณ์ Python และเป็นสถานที่ที่มีการเชื่อมต่อคำสั่งใหม่ตัวแยกวิเคราะห์จะรับรู้คำสั่งใหม่นั่นคือไฟล์ต้นฉบับที่มีมันจะถูกแยกวิเคราะห์ คอมไพเลอร์จะปฏิเสธเพราะยังต้องเปลี่ยนเป็น Python ที่ถูกต้อง
iii) ตอนนี้เราต้องเพิ่มความหมายของคำสั่ง สำหรับสิ่งนี้ต้องแก้ไข msytmts / langlet.py และเพิ่มผู้เยี่ยมชมโหนด my_stmt
def call_my_stmt(expression):
"defines behaviour for my_stmt"
print "my stmt called with", expression
class LangletTransformer(Transformer):
@transform
def my_stmt(self, node):
_expr = find_node(node, symbol.expr)
return any_stmt(CST_CallFunc("call_my_stmt", [_expr]))
__publish__ = ["call_my_stmt"]
iv) cd เป็น langlets / mystmts และพิมพ์
python run_mystmts.py
ตอนนี้เซสชันจะเริ่มต้นและสามารถใช้คำสั่งที่กำหนดใหม่ได้:
__________________________________________________________________________________
mystmts
On Python 2.5.1 (r251:54863, Apr 18 2007, 08:51:08) [MSC v.1310 32 bit (Intel)]
__________________________________________________________________________________
my> mystatement 40+2
my stmt called with 42
มีขั้นตอนไม่กี่ขั้นตอนกว่าจะมาถึงคำแถลงการณ์ใช่ไหม? ยังไม่มี API ที่ช่วยให้กำหนดสิ่งง่ายๆโดยไม่ต้องสนใจไวยากรณ์ แต่ EE เป็นโมดูโลที่น่าเชื่อถือมาก ดังนั้นจึงเป็นเพียงเรื่องของเวลาที่ API จะปรากฏขึ้นซึ่งช่วยให้โปรแกรมเมอร์สามารถกำหนดสิ่งที่สะดวกเช่นตัวดำเนินการ infix หรือข้อความขนาดเล็กโดยใช้การเขียนโปรแกรม OO ที่สะดวกสบาย สำหรับสิ่งที่ซับซ้อนมากขึ้นเช่นการฝังทั้งภาษาใน Python โดยการสร้าง langlet จะไม่มีทางใช้ไวยากรณ์แบบเต็ม
นี่คือวิธีที่ง่ายมาก แต่เส็งเคร็งที่จะเพิ่มงบใหม่ในโหมดการสื่อความหมายเท่านั้น ฉันใช้มันสำหรับคำสั่งตัวอักษร 1 ตัวเล็ก ๆ สำหรับการแก้ไขคำอธิบายประกอบยีนโดยใช้ sys.displayhook เท่านั้น แต่ฉันก็สามารถตอบคำถามนี้ได้ฉันจึงเพิ่ม sys.excepthook สำหรับข้อผิดพลาดทางไวยากรณ์ด้วย อันหลังน่าเกลียดมากโดยดึงรหัสดิบจากบัฟเฟอร์อ่านไลน์ ข้อดีคือการเพิ่มข้อความใหม่ด้วยวิธีนี้เป็นเรื่องง่ายเล็กน้อย
jcomeau@intrepid:~/$ cat demo.py; ./demo.py
#!/usr/bin/python -i
'load everything needed under "package", such as package.common.normalize()'
import os, sys, readline, traceback
if __name__ == '__main__':
class t:
@staticmethod
def localfunction(*args):
print 'this is a test'
if args:
print 'ignoring %s' % repr(args)
def displayhook(whatever):
if hasattr(whatever, 'localfunction'):
return whatever.localfunction()
else:
print whatever
def excepthook(exctype, value, tb):
if exctype is SyntaxError:
index = readline.get_current_history_length()
item = readline.get_history_item(index)
command = item.split()
print 'command:', command
if len(command[0]) == 1:
try:
eval(command[0]).localfunction(*command[1:])
except:
traceback.print_exception(exctype, value, tb)
else:
traceback.print_exception(exctype, value, tb)
sys.displayhook = displayhook
sys.excepthook = excepthook
>>> t
this is a test
>>> t t
command: ['t', 't']
this is a test
ignoring ('t',)
>>> ^D
ฉันพบคำแนะนำเกี่ยวกับการเพิ่มข้อความใหม่:
https://troeger.eu/files/teaching/pythonvm08lab.pdf
โดยพื้นฐานแล้วในการเพิ่มคำสั่งใหม่คุณต้องแก้ไข Python/ast.c
(เหนือสิ่งอื่นใด) และคอมไพล์ไบนารี python ใหม่
แม้ว่าจะเป็นไปได้ แต่อย่า คุณสามารถบรรลุเกือบทุกอย่างผ่านฟังก์ชั่นและคลาส (ซึ่งไม่จำเป็นต้องให้คนคอมไพล์ไพ ธ อนซ้ำเพื่อเรียกใช้สคริปต์ของคุณ .. )
ทำได้โดยใช้EasyExtend :
EasyExtend (EE) เป็นตัวสร้างพรีโปรเซสเซอร์และเฟรมเวิร์ก metaprogramming ที่เขียนด้วย Python บริสุทธิ์และรวมเข้ากับ CPython จุดประสงค์หลักของ EasyExtend คือการสร้างภาษาส่วนขยายเช่นการเพิ่มไวยากรณ์และความหมายที่กำหนดเองให้กับ Python
ไม่ใช่การเพิ่มคำสั่งใหม่ให้กับไวยากรณ์ของภาษา แต่มาโครเป็นเครื่องมือที่มีประสิทธิภาพ: https://github.com/lihaoyi/macropy
ไม่ใช่โดยไม่ต้องปรับเปลี่ยนล่าม ฉันรู้ว่าหลาย ๆ ภาษาในช่วงหลายปีที่ผ่านมาถูกอธิบายว่า "ขยายได้" แต่ไม่ใช่ในแบบที่คุณอธิบาย คุณขยาย Python โดยการเพิ่มฟังก์ชันและคลาส
มีภาษาที่ใช้ python เรียกว่าLogixซึ่งคุณสามารถทำสิ่งนั้นได้ ไม่ได้อยู่ระหว่างการพัฒนามาระยะหนึ่งแล้ว แต่คุณลักษณะที่คุณขอใช้งานได้กับเวอร์ชันล่าสุด
บางอย่างสามารถทำได้ด้วยมัณฑนากร สมมติว่า Python ไม่มีwith
คำสั่ง จากนั้นเราสามารถใช้พฤติกรรมที่คล้ายกันเช่นนี้:
# ====== Implementation of "mywith" decorator ======
def mywith(stream):
def decorator(function):
try: function(stream)
finally: stream.close()
return decorator
# ====== Using the decorator ======
@mywith(open("test.py","r"))
def _(infile):
for l in infile.readlines():
print(">>", l.rstrip())
มันเป็นวิธีแก้ปัญหาที่ค่อนข้างไม่สะอาด แต่ทำที่นี่ โดยเฉพาะอย่างยิ่งพฤติกรรมที่มัณฑนากรเรียกฟังก์ชั่นและชุด_
ที่จะNone
เป็นที่ไม่คาดคิด เพื่อความกระจ่าง: มัณฑนากรนี้เทียบเท่ากับการเขียน
def _(infile): ...
_ = mywith(open(...))(_) # mywith returns None.
และมัณฑนากรโดยปกติคาดว่าจะแก้ไขไม่ให้ดำเนินการฟังก์ชัน
ฉันเคยใช้วิธีดังกล่าวมาก่อนในสคริปต์ซึ่งฉันต้องตั้งไดเร็กทอรีการทำงานชั่วคราวสำหรับฟังก์ชันต่างๆ
สิบปีที่แล้วคุณทำไม่ได้และฉันสงสัยว่ามันเปลี่ยนไป อย่างไรก็ตามมันไม่ยากที่จะแก้ไขไวยากรณ์ในตอนนั้นหากคุณพร้อมที่จะคอมไพล์ python ใหม่และฉันสงสัยว่ามันเปลี่ยนไปเช่นกัน