IT박스

파이썬 구문에 새로운 문장을 추가 할 수 있습니까?

itboxs 2020. 7. 21. 08:10
반응형

파이썬 구문에 새로운 문장을 추가 할 수 있습니까?


당신은 새로운 문을 (같은 추가 할 수 print, raise, with파이썬의 구문)?

말해봐 ..

mystatement "Something"

또는,

new_if True:
    print "example"

당신이 해야하는 것이 아니라 가능하다면 (파이썬 인터프리터 코드를 수정하는 것이 부족한 경우)


-이 유용하게 사용할 수있는 파이썬에 새로운 문 추가 : 파이썬 내부를 여기에 인용을 :


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

이 기사의 모든 코딩은 Python Mercurial 저장소 미러 의 최첨단 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파이썬에 문장을 추가하는 것을 제안하지는 않습니다 . 그러한 진술이 코드를 더 명확하게 만들 것이라고 생각하지만이 기사는 코드를 추가하는 것이 얼마나 쉬운지를 보여 주지만 Python의 미니멀리즘 철학을 완전히 존중합니다. 내가 여기서하려고하는 것은 실제로 파이썬의 내부 작업에 대한 통찰력을 얻는 것입니다.

문법 수정

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

[1] : 여기서부터 파이썬 소스의 파일에 대한 참조는 소스 트리의 루트에 상대적으로 주어집니다. 소스 트리의 루트는 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 of Python과 잘 어울리지 않는다고 생각하기 때문에).

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

당신이 실행하면 make수정 한 후 Grammar/Grammar, 통지 것을 pgen프로그램이 실행되어 다시 생성 Include/graminit.h하고 Python/graminit.c, 다음 몇 가지 파일을 재 컴파일 얻을.

AST 생성 코드 수정

파이썬 파서가 파싱 트리를 생성 한 후,이 트리는 AST로 변환되는데, AST 는 컴파일 프로세스의 후속 단계에서 작업하기훨씬 간단하기 때문입니다.

그래서 우리는 방문을거야 Parser/Python.asdl파이썬하는 AST의 구조를 정의하고 우리의 새로운에 대한 AST 노드를 추가 until바로 아래에 다시 문 while:

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

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

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차이점과 함께 동등한 것을 자세히 보면서 코딩되었습니다 . 예상 한대로 AST는 조건식 및 명령문 본문 같은 다른 AST 작성 함수를 사용하여 재귀 적으로 작성됩니다 . 마지막으로 명명 된 새 노드 가 반환됩니다.untilelseast_for_exprast_for_suiteuntilUntil

and와 n같은 일부 매크로를 사용하여 구문 분석 트리 노드에 액세스합니다 . 이것들은 이해할만한 가치가 있습니다-그들의 코드가 있습니다 .NCHCHILDInclude/node.h

탈선 : AST 구성

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

until condition:
   # do stuff

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

while not condition:
  # do stuff

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

AST를 바이트 코드로 컴파일

다음 단계는 AST를 파이썬 바이트 코드로 컴파일하는 것입니다. 컴파일에는 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소스 트리 양은 그것이 호출되는 위치를 보여주지 않습니다 . 이 경우 하나의 옵션 만 남습니다 (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있는 모듈 설명서를 살펴보면 진행 상황을 이해할 수 있습니다.

그게 다야, 우리는 끝났어 ... 그렇지 않습니까?

모든 변경을 수행하고 running 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)바이트 코드를 표시하는 대신 함수 실행 ( ) 을 시도 했습니다. 그 결과는 다음과 같은 것이 었습니다.

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 컴파일러가 수행하는 단계 중 하나는 컴파일하는 코드에 대한 기호 테이블을 작성하는 것입니다. 호출 은 코드 생성 함수와 유사한 방식으로 AST를 걷는 기호 테이블 모듈 ( ) PySymtable_BuildPyAST_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;

[3]: By the way, without this code there’s a compiler warning for Python/symtable.c. The compiler notices that the Until_kind enumeration value isn’t handled in the switch statement of symtable_visit_stmt and complains. It’s always important to check for compiler warnings!

And now we really are done. Compiling the source after this change makes the execution of myfoo(3) work as expected.

Conclusion

In this article I've demonstrated how to add a new statement to Python. Albeit requiring quite a bit of tinkering in the code of the Python compiler, the change wasn't difficult to implement, because I used a similar and existing statement as a guideline.

The Python compiler is a sophisticated chunk of software, and I don't claim being an expert in it. However, I am really interested in the internals of Python, and particularly its front-end. Therefore, I found this exercise a very useful companion to theoretical study of the compiler's principles and source code. It will serve as a base for future articles that will get deeper into the compiler.

References

I used a few excellent references for the construction of this article. Here they are, in no particular order:

  • PEP 339: Design of the CPython compiler - probably the most important and comprehensive piece of official documentation for the Python compiler. Being very short, it painfully displays the scarcity of good documentation of the internals of Python.
  • "Python Compiler Internals" - an article by Thomas Lee
  • "Python: Design and Implementation" - a presentation by Guido van Rossum
  • Python (2.5) Virtual Machine, A guided tour - a presentation by Peter Tröger

original source


One way to do things like this is to preprocess the source and modify it, translating your added statement to python. There are various problems this approach will bring, and I wouldn't recommend it for general usage, but for experimentation with language, or specific-purpose metaprogramming, it can occassionally be useful.

For instance, lets say we want to introduce a "myprint" statement, that instead of printing to the screen instead logs to a specific file. ie:

myprint "This gets logged to file"

would be equivalent to

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

There are various options as to how to do the replacing, from regex substitution to generating an AST, to writing your own parser depending on how close your syntax matches existing python. A good intermediate approach is to use the tokenizer module. This should allow you to add new keywords, control structures etc while interpreting the source similarly to the python interpreter, thus avoiding the breakage crude regex solutions would cause. For the above "myprint", you could write the following transformation code:

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

(This does make myprint effectively a keyword, so use as a variable elsewhere will likely cause problems)

The problem then is how to use it so that your code is usable from python. One way would just be to write your own import function, and use it to load code written in your custom language. ie:

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

This requires you handle your customised code differently from normal python modules however. ie "some_mod = myimport("some_mod.py")" rather than "import some_mod"

Another fairly neat (albeit hacky) solution is to create a custom encoding (See PEP 263) as this recipe demonstrates. You could implement this as:

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)

Now after this code gets run (eg. you could place it in your .pythonrc or site.py) any code starting with the comment "# coding: mylang" will automatically be translated through the above preprocessing step. eg.

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

Caveats:

There are problems to the preprocessor approach, as you'll probably be familiar with if you've worked with the C preprocessor. The main one is debugging. All python sees is the preprocessed file which means that text printed in the stack trace etc will refer to that. If you've performed significant translation, this may be very different from your source text. The example above doesn't change line numbers etc, so won't be too different, but the more you change it, the harder it will be to figure out.


Yes, to some extent it is possible. There is a module out there that uses sys.settrace() to implement goto and comefrom "keywords":

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"

Short of changing and recompiling the source code (which is possible with open source), changing the base language is not really possible.

Even if you do recompile the source, it wouldn't be python, just your hacked-up changed version which you need to be very careful not to introduce bugs into.

However, I'm not sure why you'd want to. Python's object-oriented features makes it quite simple to achieve similar results with the language as it stands.


General answer: you need to preprocess your source files.

More specific answer: install EasyExtend, and go through following steps

i) Create a new langlet ( extension language )

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

Without additional specification a bunch of files shall be created under EasyExtend/langlets/mystmts/ .

ii) Open mystmts/parsedef/Grammar.ext and add following lines

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

This is sufficient to define the syntax of your new statement. The small_stmt non-terminal is part of the Python grammar and it's the place where the new statement is hooked in. The parser will now recognize the new statement i.e. a source file containing it will be parsed. The compiler will reject it though because it still has to be transformed into valid Python.

iii) Now one has to add semantics of the statement. For this one has to edit msytmts/langlet.py and add a my_stmt node visitor.

 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 to langlets/mystmts and type

python run_mystmts.py

Now a session shall be started and the newly defined statement can be used:

__________________________________________________________________________________

 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

Quite a few steps to come to a trivial statement, right? There isn't an API yet that lets one define simple things without having to care about grammars. But EE is very reliable modulo some bugs. So it's just a matter of time that an API emerges that lets programmers define convenient stuff like infix operators or small statements using just convenient OO programming. For more complex things like embedding whole languages in Python by means of building a langlet there is no way of going around a full grammar approach.


Here's a very simple but crappy way to add new statements, in interpretive mode only. I'm using it for little 1-letter commands for editing gene annotations using only sys.displayhook, but just so I could answer this question I added sys.excepthook for the syntax errors as well. The latter is really ugly, fetching the raw code from the readline buffer. The benefit is, it's trivially easy to add new statements this way.


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


I've found a guide on adding new statements:

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

Basically, to add new statements, you must edit Python/ast.c (among other things) and recompile the python binary.

While it's possible, don't. You can achieve almost everything via functions and classes (which wont require people to recompile python just to run your script..)


It's possible to do this using EasyExtend:

EasyExtend (EE) is a preprocessor generator and metaprogramming framework written in pure Python and integrated with CPython. The main purpose of EasyExtend is the creation of extension languages i.e. adding custom syntax and semantics to Python.


It's not exactly adding new statements to the language syntax, but macros are a powerful tool: https://github.com/lihaoyi/macropy


Not without modifying the interpreter. I know a lot of languages in the past several years have been described as "extensible", but not in the way you're describing. You extend Python by adding functions and classes.


There is a language based on python called Logix with which you CAN do such things. It hasn't been under development for a while, but the features that you asked for do work with the latest version.


Some things can be done with decorators. Let's e.g. assume, Python had no with statement. We could then implement a similar behaviour like this:

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

It is a pretty unclean solution however as done here. Especially the behaviour where the decorator calls the function and sets _ to None is unexpected. For clarification: This decorator is equivalent to writing

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

and decorators are normally expected to modify, not to execute, functions.

I used such a method before in a script where I had to temporarily set the working directory for several functions.


Ten years ago you couldn't, and I doubt that's changed. However, it wasn't that hard to modify the syntax back then if you were prepared to recompile python, and I doubt that's changed, either.

참고URL : https://stackoverflow.com/questions/214881/can-you-add-new-statements-to-pythons-syntax

반응형