Python의 구문에 새 문을 추가 할 수 있습니까?


124

Python의 구문 에 새 문 (예 print: raise,, with)을 추가 할 수 있습니까 ?

허용하라고 ..

mystatement "Something"

또는,

new_if True:
    print "example"

해야 한다면 그다지 많지 않지만 가능하다면 (파이썬 인터프리터 코드를 수정 하지 않음 )


10
다소 관련이있는 참고로, 즉석에서 새 명령문을 작성하는 것이 편리 할 수있는 사용 사례 중 하나는 대화 형 인터프리터를 계산기 또는 OS 셸로 사용하는 사람들을위한 것입니다. . 나는 종종 내가 반복 할 일을하기 위해 즉석에서 약간의 일회용 함수를 만들고, 그런 상황에서는 function () 구문으로 긴 이름을 입력하는 것보다 매크로 나 문장과 같은 매우 축약 된 명령을 만드는 것이 좋을 것입니다. 물론 그것이 Py의 목적은 아닙니다.하지만 사람들은 그것을 대화식으로 사용하는 데 많은 시간을 보냅니다.
Kilo

5
@Kilo ipython을 살펴볼 가치가 있습니다. 예를 들어 일반 "ls"및 "cd"명령, 탭 완성, 많은 매크로 기능 등을 사용할 수 있습니다.
dbr

Forth 및 Smalltalk와 같은 일부 언어는 절묘하게 확장 가능하지만 언어 패러다임은 Python에서 사용하는 것과 다릅니다. 이 두 가지 모두 새로운 단어 (Forth) 또는 방법 (Smalltalk)은 해당 설치 언어의 통합적이고 구별 할 수없는 부분이됩니다. 따라서 각 Forth 또는 Smalltalk 설치는 시간이 지남에 따라 고유 한 창작물이됩니다. 또한 Forth는 RPN 기반입니다. 하지만 DSL 라인을 따라 생각해 보면 이와 같은 일이 파이썬에서 가능해야합니다. 다른 사람들이 여기에서 말했듯이 왜?

1
Python과 Forth에 능통하고 과거에 여러 Forth 컴파일러를 구현 한 사람으로서 저는 여기에 어느 정도 권한을 가지고 기여할 수 있습니다. Python의 내부 파서에 대한 원시 액세스 권한이 없으면 완전히 불가능합니다. 아래의 (솔직히, 다소 매끄럽다!) 답변이 설명 하듯이 전처리를 통해 속일 수 있지만, 핫 인터프리터에서 언어의 구문 및 / 또는 의미를 진정으로 업데이트하는 것은 불가능합니다. 이것은 Python의 저주이자 Lisp 및 Forth와 유사한 언어에 비해 장점입니다.
Samuel A. Falvo II

답변:


153

유용한 정보 -Python 내부 : Python에 새 문 추가 , 여기에 인용 :


이 기사는 파이썬의 프런트 엔드가 어떻게 작동하는지 더 잘 이해하기위한 시도입니다. 문서와 소스 코드를 읽는 것만으로도 약간 지루할 수 있으므로 여기서 실습 접근 방식을 취하고 있습니다. untilPython에 명령문을 추가하겠습니다 .

이 기사의 모든 코딩은 Python Mercurial 저장소 미러 의 최첨단 Py3k 브랜치에 대해 수행되었습니다 .

until

Ruby와 같은 일부 언어에는 ( 는)를 until보완 하는 문이 있습니다. Ruby에서는 다음과 같이 작성할 수 있습니다.whileuntil num == 0while 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 파이썬에 문장을 . 이러한 진술이 코드를 더 명확하게 해줄 것이라고 생각하고이 기사에서는 추가가 얼마나 쉬운 지 보여 주지만, 저는 파이썬의 미니멀리즘 철학을 완전히 존중합니다. 여기서 제가하려는 것은 파이썬의 내부 작동에 대한 통찰력을 얻는 것입니다.

문법 수정

Python은라는 사용자 지정 파서 생성기를 사용합니다 pgen. 이것은 파이썬 소스 코드를 파스 트리로 변환하는 LL (1) 파서입니다. 파서 생성기에 대한 입력은 파일입니다 Grammar/Grammar[1] 입니다. 이것은 Python의 문법을 지정하는 간단한 텍스트 파일입니다.

[1] : 여기서부터 Python 소스의 파일에 대한 참조는 Python을 빌드하기 위해 configure 및 make를 실행하는 디렉토리 인 소스 트리의 루트에 상대적으로 제공됩니다.

문법 파일을 두 가지 수정해야합니다. 첫 번째는 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과 잘 맞지 않는다고 생각하기 때문입니다).

두 번째 변경 사항은 위의 스 니펫에서 볼 수 있듯이을 compound_stmt포함 하도록 규칙을 수정하는 것입니다 until_stmt. 바로 직후while_stmt 다시.

make수정 후 실행 Grammar/Grammar하면 pgen프로그램이 실행되어 재생성 Include/graminit.h되고Python/graminit.c 한 다음 여러 파일이 다시 컴파일됩니다.

AST 생성 코드 수정

Python 파서가 구문 분석 트리를 만든 후이 트리는 AST로 변환됩니다. AST는 작업하기훨씬 더 간단하기 때문입니다. 는 컴파일 프로세스의 후속 단계에서 때문입니다.

그래서 우리는 Parser/Python.asdl파이썬의 AST의 구조를 정의하는 곳 을 방문 하고 우리의 새로운 until문장을 위한 AST 노드를 추가 할 것입니다 while.

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

이제를 실행 make하면 여러 파일을 컴파일하기 전에 Parser/asdl_c.pyAST 정의 파일에서 C 코드를 생성하기 위해 실행됩니다. 이것은 Grammar/Grammar프로그래밍을 단순화하기 위해 미니 언어 (즉, DSL)를 사용하는 Python 소스 코드의 또 다른 예입니다. 또한 Parser/asdl_c.py은 Python 스크립트이므로 일종의 부트 스트랩은 - 처음부터 파이썬을 구축, 파이썬은 이미 사용할 수 있습니다.

Parser/asdl_c.py새로 정의 된 AST 노드 (파일 Include/Python-ast.hPython/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차이점을 제외 하고는 동등한을 면밀히 살펴보면서 코딩되었습니다 . 예상대로 AST는 조건 표현식 및 본문 과 같은 다른 AST 생성 함수를 사용하여 재귀 적으로 생성됩니다 .untilelseast_for_exprast_for_suiteuntil 문의 . 마지막으로 이름 Until이 지정된 새 노드 가 반환됩니다.

주 우리는 구문 분석 트리 노드에 액세스하는 것이 n같은 몇 가지 매크로를 사용을 NCH하고CHILD . 이것들은 이해할 가치가 있습니다-그들의 코드는 Include/node.h.

Digression : AST 구성

나는 until성명서에 대해 새로운 유형의 AST를 만들기로 선택 했지만 실제로는 이것이 필요하지 않습니다. 다음과 같은 이유로 기존 AST 노드의 구성을 사용하여 일부 작업을 저장하고 새로운 기능을 구현할 수있었습니다.

until condition:
   # do stuff

기능적으로 다음과 같습니다.

while not condition:
  # do stuff

에서 Until노드 를 만드는 대신 노드가있는 노드를 자식으로 ast_for_until_stmt만들 수 있습니다 . AST 컴파일러는 이러한 노드를 처리하는 방법을 이미 알고 있으므로 프로세스의 다음 단계를 건너 뛸 수 있습니다.NotWhile

AST를 바이트 코드로 컴파일

다음 단계는 AST를 Python 바이트 코드로 컴파일하는 것입니다. 컴파일에는 CFG (Control Flow Graph)라는 중간 결과가 있지만 동일한 코드가이를 처리하므로 지금은이 세부 사항을 무시하고 다른 기사로 남겨 두겠습니다.

다음에 살펴볼 코드는 Python/compile.c. 의 리드에 따라 명령문을 바이트 코드로 컴파일 while하는 함수를 찾습니다 compiler_visit_stmt. 다음에 대한 절을 추가합니다 Until.

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

무엇인지 궁금하다면 AST 정의 파일에서 자동으로 생성 된 Until_kind상수 (실제로 _stmt_kind열거 형 값)입니다.Include/Python-ast.h . 어쨌든 우리는compiler_until 는 물론 아직 존재하지 않는 합니다. 잠시만 요.

나처럼 호기심이 많다면 그게 compiler_visit_stmt특이 하다는 것을 알 수있을 것이다 . grep소스 트리가 호출 된 위치를 보여주는 -ping의 양은 없습니다 . 이 경우 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;
}

고백 할 게 있습니다.이 코드는 파이썬 바이트 코드에 대한 깊은 이해를 기반으로 작성되지 않았습니다. 기사의 나머지 부분과 마찬가지로 친족 compiler_while기능 을 모방하여 수행되었습니다 . 그러나주의 깊게 읽으면 Python VM이 스택 기반이라는 점을 명심하고 Python 바이트 코드 목록dis있는 모듈 문서를 살펴 봅니다. 설명 무슨 일이 일어나고 있는지 이해할 수 있습니다.

그게 다야, 우리는 끝났어. 안 그래?

모든 변경을 수행하고를 실행 한 후 make새로 컴파일 된 Python을 실행하고 새 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)바이트 코드를 표시하는 대신 함수를 실행 (executing ) 해 보았습니다 . 그 결과는 고무적이지 않았습니다.

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'

워 ... 이건 좋지 않아. 그래서 무엇이 잘못 되었습니까?

기호 표가없는 경우

AST를 컴파일 할 때 Python 컴파일러가 수행하는 단계 중 하나는 컴파일하는 코드에 대한 기호 테이블을 만드는 것입니다. 에 대한 호출 PySymtable_Build에서 PyAST_Compile심볼 테이블 모듈에 호출 (Python/symtable.c 코드 생성 기능이 유사한 방식으로 안내 AST). 각 범위에 대한 기호 테이블이 있으면 컴파일러가 전역 변수 및 범위에 로컬 인 변수와 같은 몇 가지 주요 정보를 파악하는 데 도움이됩니다.

문제를 해결하기 위해 우리는 문 을 처리하기위한 코드를 추가 symtable_visit_stmt하고 Python/symtable.c, 문 [3]에until 대한 유사한 코드 뒤에 함수 를 수정해야 합니다 .while

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 컴파일러는 정교한 소프트웨어 청크이며, 나는 그것에 대한 전문가라고 주장하지 않습니다. 그러나 저는 파이썬의 내부, 특히 프런트 엔드에 정말 관심이 있습니다. 따라서이 연습이 컴파일러의 원리와 소스 코드에 대한 이론적 연구에 매우 유용한 동반자임을 알았습니다. 컴파일러에 대해 더 자세히 살펴볼 향후 기사의 기반이 될 것입니다.

참고 문헌

이 기사의 구성을 위해 몇 가지 훌륭한 참고 자료를 사용했습니다. 여기에 특별한 순서가 없습니다.

  • PEP 339 : CPython 컴파일러 설계 -아마도 Python 컴파일러에 대한 공식 문서 중 가장 중요하고 포괄적 인 부분 일 것입니다 . 매우 짧기 때문에 파이썬 내부에 ​​대한 좋은 문서가 부족하다는 것을 고통스럽게 보여줍니다.
  • "Python 컴파일러 내부"-Thomas Lee의 기사
  • "Python : 설계 및 구현"-Guido van Rossum의 프레젠테이션
  • Python (2.5) 가상 머신, 둘러보기-Peter Tröger의 프레젠테이션

원본 소스


7
훌륭한 기사 (/ 블로그), 감사합니다! 이것이 질문에 완벽하게 답하기 때문에 수락하고 "하지 마세요"/ "coding : mylang"답변은 이미 많이 찬성되었으므로 순서대로 멋지게 나타날 것입니다. \ o /
dbr

1
그러나 불행히도 이것은 대답이 아닙니다. 링크 된 기사는 있지만 찬성하거나 수락 할 수 없습니다. 전적으로 링크로만 구성된 답변은 권장하지 않습니다.
Alfe

6
@Alfe : 2 년 전에 게시되었으며 16 명의 독자가 수락하고 +1했습니다. 내 블로그 게시물에 연결되며 큰 기사를 StackOverflow에 복사하는 것은 내가 의도 한 것이 아닙니다. 경찰이 아닌 유용한 편집으로 자유롭게 수행하십시오.
Eli Bendersky

2
@EliBendersky Useful은 그 기사에 대해 상당히 과소 평가되었습니다. 이러한 것들이 실제로 파이썬에서 어떻게 작동하는지에 대해 설명해 주셔서 감사합니다. 이것은 현재 작업과 관련된 AST를 이해하는 데 정말 도움이되었습니다. ** 또한 궁금한 untilisaisanif something isa dict:if something isan int:
점이 있으시면

5
Soo,이 대답은 "파이썬에서 분기 된 소스에서 자신의 언어를 작성하고 컴파일"입니다.
ThorSummoner

53

이와 같은 작업을 수행하는 한 가지 방법은 소스를 전처리하고 수정하여 추가 된 명령문을 파이썬으로 변환하는 것입니다. 이 접근 방식에는 여러 가지 문제가 있으며 일반적인 사용에는 권장하지 않지만 언어 실험이나 특정 목적의 메타 프로그래밍에는 유용 ​​할 수 있습니다.

예를 들어, 화면에 인쇄하는 대신 특정 파일에 기록하는 "myprint"문을 도입하려고한다고 가정 해 보겠습니다. 즉 :

myprint "This gets logged to file"

다음과 같을 것입니다

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

정규식 대체에서 AST 생성, 구문이 기존 파이썬과 얼마나 가까운 지에 따라 자신의 파서를 작성하는 방법에 대한 다양한 옵션이 있습니다. 좋은 중간 접근법은 토크 나이저 모듈을 사용하는 것입니다. 이를 통해 파이썬 인터프리터와 유사하게 소스를 해석하는 동안 새로운 키워드, 제어 구조 등을 추가 할 수 있으므로 원유 정규식 솔루션으로 인한 파손을 피할 수 있습니다. 위의 "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를 효과적으로 키워드로 만들므로 다른 곳에서 변수로 사용하면 문제가 발생할 수 있습니다)

문제는 코드를 파이썬에서 사용할 수 있도록 사용하는 방법입니다. 한 가지 방법은 직접 가져 오기 함수를 작성하고이를 사용하여 사용자 지정 언어로 작성된 코드를로드하는 것입니다. 즉 :

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

그러나 일반 파이썬 모듈과 다르게 사용자 정의 된 코드를 처리해야합니다. 즉 " 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 전처리기로 작업 한 적이 있다면 아마 익숙 할 것입니다. 주요한 것은 디버깅입니다. 파이썬이 보는 모든 것은 전처리 된 파일이므로 스택 추적 등에 인쇄 된 텍스트가 해당 파일을 참조합니다. 중요한 번역을 수행 한 경우 원본 텍스트와 매우 다를 수 있습니다. 위의 예는 줄 번호 등을 변경하지 않으므로 너무 다르지 않지만 더 많이 변경할수록 파악하기가 더 어려워집니다.


12
멋지네요! 'ca n't be dun'이라고 말하는 대신 실제로 몇 가지 좋은 답변을 제공합니다 ( '정말이 작업을하고 싶지 않습니다'로 요약됩니다).
c0m4

나는 확실히 내가 어떻게 첫 번째 예제의 작품을 이해하고 있지 않다 - 사용하려고 myimport단지에 포함 된 모듈에 print 1이 코드 수율의 선의로=1 ... SyntaxError: invalid syntax
olamundo

@noam : 무엇이 실패했는지 잘 모르겠습니다. 여기서 예상대로 "1"이 인쇄됩니다. (이것은 위의 "import tokenize"및 "import new"를 시작하는 2 개의 블록을 a.py 파일에 넣고 " b=myimport("b.py")"및 b.py에 " print 1" 만 포함하는 것 입니다. 오류에 더 많은 것이 있습니까 (스택 추적 등)?
Brian

3
Python3은이를 허용하지 않는 것 같지만, 반드시 의도 된 것은 아닙니다. BOM 오류가 발생합니다.
Tobu

import내장 을 사용 __import__하므로이를 덮어 쓰면 ( 수정 된 가져 오기가 필요한 모듈을 가져 오기 전에 ) 별도의 작업이 필요하지 않습니다myimport
Tobias Kienzler

21

예, 어느 정도 가능합니다. 구현 및 "키워드"에 사용 하는 모듈 이 있습니다 .sys.settrace()gotocomefrom

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
하지만 정말 새로운 구문은 아닙니다.
Hans Nowak

3
-1 : 링크 된 페이지에는 다음과 같은 제목이 있습니다. " 'goto'모듈은 2004 년 4 월 1 일에 게시 된 만우절의 농담이었습니다. 예, 작동하지만 그럼에도 불구하고 농담입니다. 실제 코드에서 사용하지 마십시오!"
Jim

6
@Jim은 -1을 재고 할 수 있습니다. 구현 메커니즘에 대해 알려줍니다. 시작하는 것이 좋습니다.
n611x007 2014-07-15

14

변경하고 (소스 코드 컴파일의 짧은 입니다 기본 언어를 변경, 오픈 소스 가능)은 정말 할 수 없습니다.

소스를 재 컴파일하더라도 파이썬이 아니라 버그를 도입하지 않도록 매우주의해야하는 해킹 된 변경된 버전 일뿐입니다.

그러나 왜 당신이 원하는지 잘 모르겠습니다. Python의 객체 지향 기능을 사용하면 해당 언어를 그대로 사용하여 유사한 결과를 쉽게 얻을 수 있습니다.


2
나는 한 점에 동의하지 않는다. 새 키워드 를 추가 하면 여전히 Python이라고 생각합니다. 기존 키워드 를 변경 하면 말했듯이 해킹에 불과합니다.
Bill the Lizard

9
새 키워드를 추가하면 Python 파생 언어가됩니다. 키워드를 변경하면 Python과 호환되지 않는 언어가됩니다.
tzot

1
키워드를 추가하면 "간단하고 배우기 쉬운 구문"과 "광범위한 라이브러리"의 요점이 누락 될 수 있습니다. 언어 기능은 거의 항상 실수라고 생각합니다 (예 : COBOL, Perl 및 PHP).
S.Lott

5
새 키워드는 식별자로 사용하는 Python 코드를 손상시킵니다.
akaihola

12

일반적인 대답 : 소스 파일을 사전 처리해야합니다.

보다 구체적인 답변 : 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) langlets / mystmts 및 유형으로 cd

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는 일부 버그 모듈로 매우 신뢰할 수 있습니다. 따라서 프로그래머가 편리한 OO 프로그래밍을 사용하여 중위 연산자 또는 작은 문과 같은 편리한 항목을 정의 할 수있는 API가 등장하는 것은 시간 문제 일뿐입니다. langlet을 구축하여 Python에 전체 언어를 임베드하는 것과 같은 더 복잡한 작업의 경우 완전한 문법 접근 방식을 사용할 방법이 없습니다.


11

해석 모드에서만 새 문장을 추가하는 매우 간단하지만 엉터리 방법이 있습니다 . 나는 sys.displayhook 만 사용하여 유전자 주석을 편집하기 위해 작은 1 글자 명령에 사용하고 있지만이 질문에 답할 수 있도록 구문 오류에 대해서도 sys.excepthook을 추가했습니다. 후자는 정말 추한데, readline 버퍼에서 원시 코드를 가져옵니다. 이점은 이러한 방식으로 새 문을 추가하는 것이 매우 쉽다는 것입니다.


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

새 진술 추가에 대한 가이드를 찾았습니다.

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

기본적으로 새 문을 추가하려면 Python/ast.c(무엇보다도) 파이썬 바이너리를 편집 하고 다시 컴파일 해야합니다 .

가능하지만하지 마십시오. 함수와 클래스를 통해 거의 모든 것을 달성 할 수 있습니다 (스크립트를 실행하기 위해 사람들이 파이썬을 다시 컴파일 할 필요가 없습니다 ..)


PDF로 실제 링크 - 그 "autonversion는"고장 하나님의 노하우 작동하지 않았 음 이제 긴 S : troeger.eu/files/teaching/pythonvm08lab.pdf
ZXX

3

EasyExtend를 사용 하여이 작업을 수행 할 수 있습니다 .

EasyExtend (EE)는 순수 Python으로 작성되고 CPython과 통합 된 전 처리기 생성기 및 메타 프로그래밍 프레임 워크입니다. EasyExtend의 ​​주요 목적은 확장 언어를 만드는 것입니다. 즉, 사용자 지정 구문과 의미를 Python에 추가하는 것입니다.


1
이 링크를 따라 가면 "EasyExtend가 죽었습니다. EE에 관심이있는 사람들을 위해 Langscape 다른 이름, 완전한 재 설계, 동일한 여정이라는 후속 프로젝트가 있습니다." 이 정보 페이지가 중단 될 위험이 있으므로 답변을 업데이트하는 것이 좋습니다.
celtschk


1

인터프리터를 수정하지 않고서는 안됩니다. 지난 몇 년 동안 많은 언어가 "확장 가능"하다고 설명되었지만 당신이 설명하는 방식은 아닙니다. 함수와 클래스를 추가하여 Python을 확장합니다.



1

데코레이터로 몇 가지 작업을 수행 할 수 있습니다. 예를 들어 파이썬에 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.

데코레이터는 일반적으로 함수를 실행하는 것이 아니라 수정해야합니다.

이전에는 여러 기능에 대해 작업 디렉토리를 임시로 설정해야하는 스크립트에서 이러한 방법을 사용했습니다.


0

10 년 전에는 그렇게 할 수 없었습니다. 그러나 파이썬을 재 컴파일 할 준비가되어 있다면 그 당시 구문을 수정하는 것은 그리 어렵지 않았고, 그것도 변경된 것 같지 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.