Bạn có thể thêm các câu lệnh mới vào cú pháp của Python không?


124

Bạn có thể thêm các báo cáo mới (như print, raise, with) để cú pháp của Python?

Nói, để cho phép ..

mystatement "Something"

Hoặc là,

new_if True:
    print "example"

Không quá nhiều nếu bạn nên , nhưng thay vào đó là nếu có thể (không sửa đổi mã trình thông dịch python)


10
Trên một lưu ý có liên quan, một trường hợp sử dụng có thể thuận tiện để tạo ra các câu lệnh mới một cách nhanh chóng (trái ngược với việc "mở rộng" ngôn ngữ một cách nghiêm túc) dành cho những người sử dụng trình thông dịch tương tác làm máy tính hoặc thậm chí là hệ điều hành . Tôi thường tạo ra các hàm vứt bỏ một cách nhanh chóng để làm điều gì đó mà tôi sẽ lặp lại và trong những tình huống đó sẽ rất tuyệt khi tạo các lệnh được viết tắt như macro hoặc câu lệnh thay vì gõ các tên dài bằng cú pháp hàm (). Tất nhiên đó không thực sự là những gì Py dành cho .. nhưng mọi người dành nhiều thời gian để sử dụng nó một cách tương tác.
Kilo

5
@Kilo nó có thể là giá trị xem xét ipython - nó có rất nhiều tính năng shell'ish, ví dụ bạn có thể sử dụng thường xuyên "ls" và "cd" lệnh, bằng tab hoàn thành, rất nhiều tính năng vĩ mô ish vv
DBR

Một số ngôn ngữ có thể mở rộng một cách tinh vi, ví dụ Forth và Smalltalk, nhưng mô hình ngôn ngữ của chúng cũng khác với ngôn ngữ được sử dụng bởi Python. Với cả hai từ đó, bất kỳ từ mới nào (Forth) hoặc phương thức (Smalltalk) đều trở thành một phần không thể tách rời, không thể phân biệt của ngôn ngữ cho bản cài đặt đó. Vì vậy, mỗi lần cài đặt Forth hoặc Smalltalk trở thành một sáng tạo độc đáo theo thời gian. Ngoài ra Forth là dựa trên RPN. Nhưng suy nghĩ theo dòng DSL, một cái gì đó như thế này có thể thực hiện được trong Python. Mặc dù, như những người khác đã nói ở đây, tại sao?

1
Là một người thông thạo cả Python và Forth, và đã thực hiện một số trình biên dịch Forth trong nhiều năm qua, tôi có thể đóng góp ở đây với một số mức độ thẩm quyền. Không có quyền truy cập thô vào trình phân tích cú pháp nội bộ của Python, điều đó hoàn toàn không thể. Bạn có thể giả mạo nó bằng cách tiền xử lý, vì các câu trả lời (thẳng thắn, khá lắt léo!) Dưới đây minh họa, nhưng thực sự cập nhật cú pháp và / hoặc ngữ nghĩa của ngôn ngữ trong một trình thông dịch nóng là không thể. Đây là cả lời nguyền của Python cũng như lợi thế của nó đối với các ngôn ngữ giống Lisp và Forth.
Samuel A. Falvo II

Câu trả lời:


153

Bạn có thể thấy điều này hữu ích - Nội bộ Python: thêm một câu lệnh mới vào Python , được trích dẫn ở đây:


Bài viết này là một nỗ lực để hiểu rõ hơn về cách thức hoạt động của Python. Chỉ đọc tài liệu và mã nguồn có thể hơi nhàm chán, vì vậy tôi đang thực hiện một cách tiếp cận thực hành ở đây: Tôi sẽ thêm một untiltuyên bố vào Python.

Tất cả các mã hóa cho bài viết này đã được thực hiện đối với nhánh Py3k tiên tiến trong máy nhân bản kho lưu trữ Python Mercurial .

các untiltuyên bố

Một số ngôn ngữ, như Ruby, có một untiltuyên bố, đó là phần bổ sung cho while( until num == 0tương đương với while num != 0). Trong Ruby, tôi có thể viết:

num = 3
until num == 0 do
  puts num
  num -= 1
end

Và nó sẽ in:

3
2
1

Vì vậy, tôi muốn thêm một khả năng tương tự với Python. Đó là, có thể viết:

num = 3
until num == 0:
  print(num)
  num -= 1

Một cuộc cải cách ngôn ngữ

Bài viết này không cố gắng đề xuất bổ sung một untiltuyên bố cho Python. Mặc dù tôi nghĩ rằng một tuyên bố như vậy sẽ làm cho một số mã rõ ràng hơn và bài viết này cho thấy việc thêm nó dễ dàng như thế nào, tôi hoàn toàn tôn trọng triết lý tối giản của Python. Tất cả những gì tôi đang cố gắng làm ở đây, thực sự, là hiểu rõ hơn về hoạt động bên trong của Python.

Sửa đổi ngữ pháp

Python sử dụng một trình tạo trình phân tích cú pháp tùy chỉnh có tên pgen. Đây là trình phân tích cú pháp LL (1) chuyển đổi mã nguồn Python thành cây phân tích cú pháp. Đầu vào của trình tạo bộ phân tích cú pháp là tệp Grammar/Grammar[1] . Đây là một tệp văn bản đơn giản chỉ định ngữ pháp của Python.

[1] : Từ đây trở đi, các tham chiếu đến các tệp trong nguồn Python được cung cấp tương đối cho thư mục gốc của cây nguồn, đây là thư mục nơi bạn chạy cấu hình và thực hiện để xây dựng Python.

Hai sửa đổi phải được thực hiện cho tập tin ngữ pháp. Đầu tiên là thêm một định nghĩa cho untilcâu lệnh. Tôi tìm thấy nơi whilecâu lệnh được định nghĩa ( while_stmt) và được thêm vào until_stmtbên dưới [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] : Điều này thể hiện một kỹ thuật phổ biến tôi sử dụng khi sửa đổi mã nguồn mà tôi không quen thuộc: làm việc bằng cách tương tự . Nguyên tắc này sẽ không giải quyết tất cả các vấn đề của bạn, nhưng nó chắc chắn có thể làm giảm quá trình. Vì tất cả mọi thứ phải được thực hiện whilecũng phải được thực hiện until, nó phục vụ như một hướng dẫn khá tốt.

Lưu ý rằng tôi đã quyết định loại trừ else mệnh đề khỏi định nghĩa của tôi until, chỉ để làm cho nó khác đi một chút (và thật lòng tôi không thích elsemệnh đề của các vòng lặp và không nghĩ rằng nó phù hợp với Zen of Python).

Thay đổi thứ hai là sửa đổi quy tắc cho compound_stmt để bao gồm until_stmt, như bạn có thể thấy trong đoạn trích ở trên. Đó là ngay sau đó while_stmt, một lần nữa.

Khi bạn chạy makesau khi sửa đổi Grammar/Grammar, hãy chú ý rằngpgen chương trình đang chạy để tái tạo Include/graminit.hPython/graminit.c, và sau đó một số tác phẩm được tái biên dịch.

Sửa đổi mã tạo AST

Sau khi trình phân tích cú pháp Python đã tạo một cây phân tích cú pháp, cây này được chuyển đổi thành AST, vì AST là đơn giản hơn nhiều để làm việc với các giai đoạn tiếp theo của quá trình biên dịch.

Vì vậy, chúng tôi sẽ truy cập vào Parser/Python.asdlđó xác định cấu trúc AST của Python và thêm nút AST cho untilcâu lệnh mới của chúng tôi , một lần nữa ngay bên dưới while:

| While(expr test, stmt* body, stmt* orelse)
| Until(expr test, stmt* body)

Nếu bây giờ bạn chạy make, hãy lưu ý rằng trước khi biên dịch một loạt các tệp, Parser/asdl_c.pyđược chạy để tạo mã C từ tệp định nghĩa AST. Đây (như Grammar/Grammar) là một ví dụ khác về mã nguồn Python sử dụng ngôn ngữ nhỏ (nói cách khác là DSL) để đơn giản hóa việc lập trình. Cũng lưu ý rằng vì Parser/asdl_c.pylà tập lệnh Python, đây là một kiểu bootstrapping - để xây dựng Python từ đầu, Python đã có sẵn.

Trong khi Parser/asdl_c.pytạo mã để quản lý nút AST mới được xác định của chúng tôi (vào các tệp Include/Python-ast.hPython/Python-ast.c), chúng tôi vẫn phải viết mã chuyển đổi nút phân tích cú pháp cây có liên quan thành nút bằng tay. Điều này được thực hiện trong tập tin Python/ast.c. Ở đó, một hàm có tên là ast_for_stmtchuyển đổi các nút cây phân tích cú pháp cho các câu lệnh thành các nút AST. Một lần nữa, được hướng dẫn bởi người bạn cũ của chúng tôi while, chúng tôi nhảy ngay vào lớn switchđể xử lý các câu lệnh ghép và thêm một mệnh đề cho until_stmt:

case while_stmt:
    return ast_for_while_stmt(c, ch);
case until_stmt:
    return ast_for_until_stmt(c, ch);

Bây giờ chúng ta nên thực hiện ast_for_until_stmt. Đây là:

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;
}

Một lần nữa, điều này đã được mã hóa trong khi xem xét tương đương ast_for_while_stmt, với sự khác biệt là untiltôi đã quyết định không hỗ trợ elseđiều khoản này. Như mong đợi, AST được tạo đệ quy, sử dụng các hàm tạo AST khác như ast_for_exprcho biểu thức điều kiện và ast_for_suitecho phần thân của untilcâu lệnh. Cuối cùng, một nút mới có tênUntil được trả về.

Lưu ý rằng chúng tôi truy cập nút phân tích cú pháp nbằng cách sử dụng một số macro như NCHCHILD. Đây là những giá trị hiểu biết - mã của họ là trongInclude/node.h .

Digression: Thành phần AST

Tôi đã chọn tạo một loại AST mới cho untiltuyên bố, nhưng thực sự điều này không cần thiết. Tôi đã có thể lưu một số công việc và triển khai chức năng mới bằng cách sử dụng thành phần của các nút AST hiện có, kể từ:

until condition:
   # do stuff

Có chức năng tương đương với:

while not condition:
  # do stuff

Thay vì tạo Untilnút trong ast_for_until_stmt, tôi có thể đã tạo một Notnút có Whilenút khi còn nhỏ. Vì trình biên dịch AST đã biết cách xử lý các nút này, các bước tiếp theo của quy trình có thể được bỏ qua.

Biên dịch AST thành mã byte

Bước tiếp theo là biên dịch AST thành mã byte Python. Quá trình biên dịch có kết quả trung gian là CFG (Control Flow Graph), nhưng vì cùng một mã xử lý nên tôi sẽ bỏ qua chi tiết này ngay bây giờ và để lại cho một bài viết khác.

Mã chúng ta sẽ xem xét tiếp theo là Python/compile.c. Theo sự dẫn dắt củawhile , chúng tôi tìm thấy hàm compiler_visit_stmtchịu trách nhiệm biên dịch các câu lệnh thành mã byte. Chúng tôi thêm một điều khoản cho Until:

case While_kind:
    return compiler_while(c, s);
case Until_kind:
    return compiler_until(c, s);

Nếu bạn tự hỏi nó Until_kindlà gì , đó là một hằng số (thực sự là một giá trị của phép _stmt_kindliệt kê) tự động được tạo từ tệp định nghĩa AST vào Include/Python-ast.h. Dù sao, chúng tôi gọicompiler_until đó, tất nhiên, vẫn không tồn tại. Tôi sẽ đến đó một lát.

Nếu bạn tò mò như tôi, bạn sẽ nhận thấy điều đó compiler_visit_stmtthật đặc biệt. Không có số lượng grep-ping cây nguồn cho thấy nơi nó được gọi. Khi gặp trường hợp này, chỉ còn một tùy chọn - C macro-fu. Thật vậy, một cuộc điều tra ngắn dẫn chúng ta đến VISITvĩ mô được xác định trong Python/compile.c:

#define VISIT(C, TYPE, V) {\
    if (!compiler_visit_ ## TYPE((C), (V))) \
        return 0; \

Nó được sử dụng để gọi compiler_visit_stmttrongcompiler_body . Quay lại với công việc của chúng tôi, tuy nhiên ...

Như đã hứa, đây 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;
}

Tôi có một lời thú nhận để thực hiện: mã này không được viết dựa trên sự hiểu biết sâu sắc về mã byte Python. Giống như phần còn lại của bài viết, nó đã được thực hiện trong việc bắt chước compiler_whilechức năng kin . Tuy nhiên, bằng cách đọc nó một cách cẩn thận, hãy nhớ rằng Python VM dựa trên stack và liếc vào tài liệu củadis mô-đun, trong đó có một danh sách các mã byte Python với các mô tả, có thể hiểu những gì đang diễn ra.

Thế là xong, chúng ta đã xong ... Phải không?

Sau khi thực hiện tất cả các thay đổi và chạy make, chúng ta có thể chạy Python mới được biên dịch và thử untiltuyên bố mới của chúng tôi :

>>> until num == 0:
...   print(num)
...   num -= 1
...
3
2
1

Voila, nó hoạt động! Chúng ta hãy xem mã byte được tạo cho câu lệnh mới bằng cách sử dụng dismô-đun như sau:

import dis

def myfoo(num):
    until num == 0:
        print(num)
        num -= 1

dis.dis(myfoo)

Đây là kết quả:

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

Hoạt động thú vị nhất là số 12: nếu điều kiện là đúng, chúng ta nhảy tới sau vòng lặp. Đây là ngữ nghĩa chính xác chountil . Nếu bước nhảy không được thực thi, thân vòng lặp sẽ tiếp tục chạy cho đến khi nó nhảy trở lại điều kiện khi hoạt động 35.

Cảm thấy tốt về sự thay đổi của mình, sau đó tôi đã thử chạy chức năng (thực thi myfoo(3)) thay vì hiển thị mã byte của nó. Kết quả không đáng khích lệ:

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'

Whoa ... điều này không thể tốt được. Vì vậy, những gì đã đi sai?

Trường hợp bảng biểu tượng bị thiếu

Một trong những bước mà trình biên dịch Python thực hiện khi biên dịch AST là tạo bảng ký hiệu cho mã mà nó biên dịch. Cuộc gọi đến PySymtable_Buildtrong PyAST_Compilecác cuộc gọi vào mô-đun bảng biểu tượng (Python/symtable.c ), đi theo AST theo cách tương tự như các hàm tạo mã. Có một bảng ký hiệu cho mỗi phạm vi giúp trình biên dịch tìm ra một số thông tin chính, chẳng hạn như biến nào là toàn cục và biến cục bộ trong phạm vi.

Để khắc phục sự cố, chúng tôi phải sửa đổi symtable_visit_stmthàm trong Python/symtable.c, thêm mã để xử lý các untilcâu lệnh, sau mã tương tự cho các whilecâu lệnh [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] : Nhân tiện, không có mã này, có một cảnh báo về trình biên dịch Python/symtable.c. Trình biên dịch thông báo rằng Until_kindgiá trị liệt kê không được xử lý trong câu lệnh chuyển đổi củasymtable_visit_stmt và phàn nàn. Nó luôn luôn quan trọng để kiểm tra các cảnh báo trình biên dịch!

Và bây giờ chúng tôi thực sự đã hoàn thành. Biên dịch nguồn sau khi thay đổi này làm cho việc thực hiện myfoo(3)công việc như mong đợi.

Phần kết luận

Trong bài viết này, tôi đã trình bày cách thêm một câu lệnh mới vào Python. Mặc dù đòi hỏi khá nhiều sự mày mò trong mã của trình biên dịch Python, sự thay đổi không khó thực hiện, vì tôi đã sử dụng một câu lệnh tương tự và hiện có làm hướng dẫn.

Trình biên dịch Python là một phần mềm tinh vi và tôi không khẳng định mình là một chuyên gia về nó. Tuy nhiên, tôi thực sự quan tâm đến phần bên trong của Python và đặc biệt là phần đầu của nó. Do đó, tôi thấy bài tập này là một người bạn đồng hành rất hữu ích để nghiên cứu lý thuyết về các nguyên tắc và mã nguồn của trình biên dịch. Nó sẽ phục vụ như là một cơ sở cho các bài viết trong tương lai sẽ đi sâu hơn vào trình biên dịch.

Người giới thiệu

Tôi đã sử dụng một vài tài liệu tham khảo tuyệt vời cho việc xây dựng bài viết này. Ở đây họ không theo thứ tự đặc biệt:

  • PEP 339: Thiết kế trình biên dịch CPython - có lẽ là phần tài liệu chính thức quan trọng và toàn diện nhất cho trình biên dịch Python. Rất ngắn, nó đau đớn hiển thị sự khan hiếm tài liệu tốt về các phần bên trong của Python.
  • "Python Compiler Internals" - một bài viết của Thomas Lee
  • "Python: Thiết kế và triển khai" - một bài thuyết trình của Guido van Rossum
  • Máy ảo Python (2.5), Chuyến tham quan có hướng dẫn - bài thuyết trình của Peter Tröger

nguồn chính thức


7
Bài viết tuyệt vời (/ blog), cảm ơn! Chấp nhận vì điều này trả lời hoàn hảo câu hỏi và câu trả lời "không làm điều đó" / "mã hóa: mylang" đã được nâng cao, vì vậy sẽ xuất hiện độc đáo theo thứ tự \ o /
dbr

1
Nhưng thật không may, đây không phải là một câu trả lời. Bài viết được liên kết là, nhưng bạn không thể nâng cấp hoặc chấp nhận. Câu trả lời bao gồm hoàn toàn chỉ là một liên kết được khuyến khích.
Alfe

6
@ Alfe: điều này đã được đăng hai năm trước, được chấp nhận và +1 1 bởi 16 độc giả. Lưu ý rằng nó liên kết đến bài đăng trên blog của riêng tôi và sao chép một bài viết lớn vào StackOverflow không phải là điều tôi dự định làm. Hãy làm điều đó trong một chỉnh sửa hữu ích, thay vì chơi cảnh sát.
Eli Bendersky

2
@EliBendersky Hữu ích là một cách đánh giá thấp cho bài viết đó. Cảm ơn vì đã giải thích rất nhiều về cách những thứ này thực sự hoạt động trong python. Điều này thực sự đã giúp tôi hiểu AST, có liên quan đến công việc hiện tại của tôi. ** cũng vậy, trong trường hợp bạn tò mò, phiên bản của tôi untilisa/ isannhư trong if something isa dict:hoặcif something isan int:
Inversus

5
Soo, câu trả lời này là "Viết và biên dịch ngôn ngữ của bạn từ nguồn, rẽ nhánh từ con trăn"
ThorSummoner

53

Một cách để làm những việc như thế này là tiền xử lý nguồn và sửa đổi nó, dịch câu lệnh đã thêm của bạn sang python. Có nhiều vấn đề khác nhau mà phương pháp này sẽ mang lại, và tôi sẽ không đề xuất nó cho sử dụng chung, nhưng để thử nghiệm với ngôn ngữ hoặc siêu lập trình cho mục đích cụ thể, đôi khi nó có thể hữu ích.

Chẳng hạn, giả sử chúng tôi muốn giới thiệu một tuyên bố "myprint", thay vì in ra màn hình thay vì ghi vào một tệp cụ thể. I E:

myprint "This gets logged to file"

sẽ tương đương với

print >>open('/tmp/logfile.txt','a'), "This gets logged to file"

Có nhiều tùy chọn khác nhau về cách thực hiện thay thế, từ thay thế regex đến tạo AST, để viết trình phân tích cú pháp của riêng bạn tùy thuộc vào mức độ cú pháp của bạn khớp với python hiện có. Một cách tiếp cận trung gian tốt là sử dụng mô-đun mã thông báo. Điều này sẽ cho phép bạn thêm từ khóa mới, cấu trúc điều khiển, vv trong khi giải thích nguồn tương tự như trình thông dịch python, do đó tránh các giải pháp regex thô bị phá vỡ sẽ gây ra. Đối với "myprint" ở trên, bạn có thể viết mã chuyển đổi sau:

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

(Điều này làm cho myprint hiệu quả là một từ khóa, vì vậy sử dụng như một biến ở nơi khác có thể sẽ gây ra vấn đề)

Vấn đề sau đó là làm thế nào để sử dụng nó để mã của bạn có thể sử dụng được từ python. Một cách sẽ chỉ là viết chức năng nhập của riêng bạn và sử dụng nó để tải mã được viết bằng ngôn ngữ tùy chỉnh của bạn. I E:

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

Điều này đòi hỏi bạn phải xử lý mã tùy chỉnh của mình khác với các mô-đun python bình thường. tức là " some_mod = myimport("some_mod.py")" chứ không phải " import some_mod"

Một giải pháp khá gọn gàng (mặc dù hacky) là tạo ra một mã hóa tùy chỉnh (Xem PEP 263 ) như công thức này thể hiện. Bạn có thể thực hiện điều này như:

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)

Bây giờ sau khi mã này được chạy (ví dụ: bạn có thể đặt nó vào .pythonrc hoặc site.py), bất kỳ mã nào bắt đầu bằng nhận xét "# mã hóa: mylang" sẽ tự động được dịch qua bước tiền xử lý ở trên. ví dụ.

# 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")

Hãy cẩn thận:

Có một số vấn đề đối với cách tiếp cận tiền xử lý, vì có lẽ bạn sẽ quen với nếu bạn đã làm việc với tiền xử lý C. Cái chính là gỡ lỗi. Tất cả các python thấy là tệp được xử lý trước, có nghĩa là văn bản được in trong theo dõi ngăn xếp, v.v. sẽ đề cập đến điều đó. Nếu bạn đã thực hiện bản dịch quan trọng, điều này có thể rất khác với văn bản nguồn của bạn. Ví dụ trên không thay đổi số dòng, v.v., vì vậy sẽ không quá khác biệt, nhưng bạn càng thay đổi nó, bạn sẽ càng khó tìm ra.


12
Đẹp quá Thay vì nói 'không thể dun', bạn thực sự đưa ra một vài câu trả lời hay (làm sôi lên 'bạn thực sự không muốn làm điều này') Upvote.
c0m4

Tôi không chắc chắn tôi hiểu cách ví dụ đầu tiên hoạt động - cố gắng sử dụng myimporttrên một mô-đun chỉ chứaprint 1 dòng sản phẩm mã duy nhất=1 ... SyntaxError: invalid syntax
olamundo

@noam: không chắc chắn điều gì làm bạn thất vọng - ở đây tôi chỉ nhận được "1" được in như mong đợi. (Đây là với 2 khối bắt đầu "nhập mã thông báo" và "nhập mới" ở trên, đặt vào tệp a.py, cũng như " b=myimport("b.py")" và b.py chỉ chứa " print 1". Có bất kỳ lỗi nào nữa không (theo dõi ngăn xếp v.v.)?
Brian

3
Python3 dường như không cho phép điều này, mặc dù không nhất thiết phải có mục đích; Tôi nhận được một lỗi BOM.
Tobu

lưu ý rằng importsử dụng nội dung __import__, vì vậy nếu bạn ghi đè lên đó ( trước khi nhập mô-đun yêu cầu nhập sửa đổi), bạn không cần riêngmyimport
Tobias Kienzler

21

Vâng, ở một mức độ nào đó là có thể. Đây là một mô-đun ngoài đó sử dụng sys.settrace()để thực hiện gotocomefrom"từ khóa":

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"

4
Đó không phải là cú pháp thực sự mới mặc dù ... nó trông giống như nó.
Hans Nowak

3
-1: Trang được liên kết có tiêu đề này: "Mô-đun 'goto' là một trò đùa Cá tháng Tư, được xuất bản vào ngày 1 tháng 4 năm 2004. Vâng, nó hoạt động, tuy nhiên đó là một trò đùa. Tuy nhiên, đừng sử dụng nó trong mã thực!"
Jim

5
@Jim có thể xem xét lại -1. nó gợi ý cho bạn về cơ chế thực hiện. điều tốt đẹp để bắt đầu với.
n611x007

14

Ngắn thay đổi và biên dịch lại mã nguồn (mà có thể với mã nguồn mở), thay đổi ngôn ngữ cơ bản là không thực sự tốt.

Ngay cả khi bạn biên dịch lại nguồn, nó sẽ không phải là python, chỉ là phiên bản đã thay đổi bị hack của bạn mà bạn cần hết sức cẩn thận để không đưa lỗi vào.

Tuy nhiên, tôi không chắc tại sao bạn muốn. Các tính năng hướng đối tượng của Python làm cho nó khá đơn giản để đạt được kết quả tương tự với ngôn ngữ.


2
Tôi không đồng ý ở một điểm. Nếu bạn thêm từ khóa mới, tôi nghĩ nó vẫn sẽ là Python. Nếu bạn thay đổi các từ khóa hiện có, thì đó chỉ là hack, như bạn nói.
Bill the Lizard

9
Nếu bạn thêm từ khóa mới, đó sẽ là ngôn ngữ có nguồn gốc từ Python. Nếu bạn thay đổi từ khóa, đó sẽ là ngôn ngữ không tương thích với Python.
tzot

1
Nếu bạn thêm từ khóa, bạn có thể thiếu điểm "cú pháp đơn giản dễ học" và "thư viện rộng lớn". Tôi nghĩ các tính năng ngôn ngữ hầu như luôn là một lỗi (ví dụ bao gồm COBOL, Perl và PHP).
S.Lott

5
Các từ khóa mới sẽ phá vỡ mã Python sử dụng chúng làm định danh.
akaihola

12

Câu trả lời chung: bạn cần xử lý trước các tệp nguồn của mình.

Câu trả lời cụ thể hơn: cài đặt EasyExtend và thực hiện các bước sau

i) Tạo một langlet mới (ngôn ngữ mở rộng)

import EasyExtend
EasyExtend.new_langlet("mystmts", prompt = "my> ", source_ext = "mypy")

Nếu không có thông số kỹ thuật bổ sung, một loạt các tệp sẽ được tạo trong EasyExtend / langlets / mystmts /.

ii) Mở mystmts / Parsedef / Grammar.ext và thêm các dòng sau

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

Điều này là đủ để xác định cú pháp của tuyên bố mới của bạn. Non-terminal non_stmt là một phần của ngữ pháp Python và đó là nơi mà câu lệnh mới được nối vào. Trình phân tích cú pháp sẽ nhận ra câu lệnh mới tức là một tệp nguồn chứa nó sẽ được phân tích cú pháp. Trình biên dịch sẽ từ chối nó mặc dù nó vẫn phải được chuyển đổi thành Python hợp lệ.

iii) Bây giờ người ta phải thêm ngữ nghĩa của tuyên bố. Đối với điều này, người ta phải chỉnh sửa msytmts / langlet.py và thêm khách truy cập nút 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 vào langlets / mystmts và gõ

python run_mystmts.py

Bây giờ một phiên sẽ được bắt đầu và câu lệnh mới được xác định có thể được sử dụng:

__________________________________________________________________________________

 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

Một vài bước để đi đến một tuyên bố tầm thường, phải không? Vẫn chưa có API cho phép người ta xác định những điều đơn giản mà không cần phải quan tâm đến ngữ pháp. Nhưng EE là modulo rất đáng tin cậy một số lỗi. Vì vậy, vấn đề chỉ là thời gian mà API xuất hiện cho phép các lập trình viên xác định các công cụ thuận tiện như toán tử infix hoặc các câu lệnh nhỏ chỉ bằng lập trình OO thuận tiện. Đối với những thứ phức tạp hơn như nhúng toàn bộ ngôn ngữ trong Python bằng cách xây dựng một langlet, không có cách nào để đi xung quanh một cách tiếp cận ngữ pháp đầy đủ.


11

Đây là một cách rất đơn giản nhưng nhảm nhí để thêm các câu lệnh mới, chỉ trong chế độ diễn giải . Tôi đang sử dụng nó cho các lệnh 1 ký tự nhỏ để chỉnh sửa chú thích gen chỉ sử dụng sys.displayhook, nhưng để tôi có thể trả lời câu hỏi này, tôi cũng đã thêm sys.ex805thook cho các lỗi cú pháp. Cái sau thực sự xấu xí, lấy mã thô từ bộ đệm readline. Lợi ích là, thật dễ dàng để thêm các tuyên bố mới theo cách này.


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

4

Tôi đã tìm thấy một hướng dẫn về việc thêm các tuyên bố mới:

https://troeger.eu/files/teaching/pythonvm08lab.pdf

Về cơ bản, để thêm các tuyên bố mới, bạn phải chỉnh sửa Python/ast.c (trong số những thứ khác) và biên dịch lại nhị phân python.

Trong khi nó có thể, đừng. Bạn có thể đạt được hầu hết mọi thứ thông qua các hàm và lớp (không yêu cầu mọi người biên dịch lại python chỉ để chạy tập lệnh của bạn ..)


Liên kết thực sự với PDF - rằng "tự động chuyển đổi" đã bị hỏng và đã bị phá vỡ vì Chúa biết từ lâu: troeger.eu/files/teaching/pythonvm08lab.pdf
ZXX

3

Có thể thực hiện việc này bằng EasyExtend :

EasyExtend (EE) là một trình tạo tiền xử lý và khung siêu lập trình được viết bằng Python thuần túy và được tích hợp với CPython. Mục đích chính của EasyExtend là tạo ra các ngôn ngữ mở rộng, tức là thêm cú pháp và ngữ nghĩa tùy chỉnh vào Python.


1
Theo liên kết đó bây giờ sẽ cho một trang: "EasyExtend đã chết. Đối với những người quan tâm đến EE, có một dự án kế tiếp có tên Langscape Tên khác, thiết kế lại hoàn chỉnh, cùng một hành trình." Vì có nguy cơ trang thông tin này có thể bị chết, có lẽ nên cập nhật câu trả lời.
celtschk


1

Không phải không sửa đổi trình thông dịch. Tôi biết rất nhiều ngôn ngữ trong nhiều năm qua đã được mô tả là "có thể mở rộng", nhưng không phải theo cách bạn mô tả. Bạn mở rộng Python bằng cách thêm các hàm và lớp.



1

Một số điều có thể được thực hiện với trang trí. Giả sử, giả sử, Python không có withtuyên bố. Sau đó chúng ta có thể thực hiện một hành vi tương tự như thế này:

# ====== 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())

Đó là một giải pháp khá ô uế tuy nhiên như được thực hiện ở đây. Đặc biệt là các hành vi nơi trang trí gọi hàm và bộ _đến Nonelà bất ngờ. Để làm rõ: Trang trí này tương đương với văn bản

def _(infile): ...
_ = mywith(open(...))(_) # mywith returns None.

và trang trí thường được dự kiến ​​sẽ sửa đổi, không thực hiện, chức năng.

Tôi đã sử dụng một phương thức như vậy trước đây trong một kịch bản mà tôi phải tạm thời đặt thư mục làm việc cho một số chức năng.


0

Mười năm trước bạn không thể, và tôi nghi ngờ điều đó đã thay đổi. Tuy nhiên, sau đó không khó để sửa đổi cú pháp nếu bạn chuẩn bị biên dịch lại python và tôi cũng nghi ngờ điều đó đã thay đổi.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.