Python の構文に新しいステートメントを追加できますか?
-
03-07-2019 - |
質問
新しいステートメントを追加していただけますか (例: print
, raise
, with
) Python の構文に?
許可すると言ってください。
mystatement "Something"
または、
new_if True:
print "example"
あなたならそれほど多くはありません すべき, 、しかし、それが可能であれば(Pythonインタープリターコードを変更せずに)
解決
これは役に立つかもしれません - Python の内部:Python に新しいステートメントを追加する, 、ここで引用:
この記事は、Python のフロントエンドがどのように機能するかをより深く理解することを目的としています。ドキュメントやソース コードを読むだけでは少し退屈かもしれないので、ここでは実践的なアプローチをとります。を追加します until
Python へのステートメント。
この記事のコーディングはすべて、 Python 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 の内部動作についての洞察を得ることだけです。
文法の修正
Python は、という名前のカスタム パーサー ジェネレーターを使用します。 pgen
. 。これは、Python ソース コードを解析ツリーに変換する LL(1) パーサーです。パーサー ジェネレーターへの入力はファイルです。 Grammar/Grammar
[1]. 。これは、Python の文法を指定する単純なテキスト ファイルです。
[1]:これ以降、Python ソース内のファイルへの参照は、ソース ツリーのルート (Python をビルドするためにconfigure および make を実行するディレクトリ) に対して相対的に指定されます。
文法ファイルに 2 つの変更を加える必要があります。1 つ目は、 until
声明。どこにあるのか見つけました while
ステートメントが定義されました (while_stmt
)、追加しました until_stmt
下に [2]:
compound_stmt: if_stmt | while_stmt | until_stmt | for_stmt | try_stmt | with_stmt | funcdef | classdef | decorated
if_stmt: 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite]
while_stmt: 'while' test ':' suite ['else' ':' suite]
until_stmt: 'until' test ':' suite
[2]:これは、私が慣れていないソース コードを変更するときに使用する一般的なテクニックを示しています。 類似性によって働く. 。この原則ですべての問題が解決されるわけではありませんが、プロセスを容易にすることは間違いありません。しなければならないことはすべてあるので、 while
のためにも行う必要があります until
, 、これはかなり良いガイドラインとして役立ちます。
を除外することにしたことに注意してください。 else
私の定義からの条項 until
, 、少し違うものにするためです(そして、率直に言って私は else
ループの節であり、Python の Zen とはうまく適合しないと思います)。
2 番目の変更は、次のルールを変更することです。 compound_stmt
含める until_stmt
, 上のスニペットでわかるように。直後です while_stmt
, 、 また。
走るとき make
修正後 Grammar/Grammar
, に注目してください。 pgen
プログラムを実行して再生成する Include/graminit.h
そして Python/graminit.c
, 、その後、いくつかのファイルが再コンパイルされます。
AST生成コードの変更
Python パーサーが解析ツリーを作成した後、AST は次のとおりであるため、このツリーは AST に変換されます。 作業がはるかに簡単です コンパイルプロセスの後続の段階で。
それで、私たちは訪問するつもりです Parser/Python.asdl
これは Python の AST の構造を定義し、新しいノードに AST ノードを追加します。 until
ステートメントのすぐ下にある while
:
| While(expr test, stmt* body, stmt* orelse)
| Until(expr test, stmt* body)
今実行すると make
, 大量のファイルをコンパイルする前に、 Parser/asdl_c.py
を実行して、AST 定義ファイルから C コードを生成します。これ(みたいな) Grammar/Grammar
) は、プログラミングを簡素化するためにミニ言語 (つまり DSL) を使用した Python ソースコードの別の例です。また、以来、 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組成
新しいタイプの AST を作成することにしました。 until
というステートメントですが、実際にはこれは必要ありません。次の理由から、作業をいくつか節約し、既存の AST ノードの構成を使用して新しい機能を実装することもできました。
until condition:
# do stuff
機能的には以下と同等です。
while not condition:
# do stuff
を作成する代わりに、 Until
ノードイン ast_for_until_stmt
, 、を作成することもできました。 Not
ノード While
ノードを子として使用します。AST コンパイラはこれらのノードの処理方法をすでに知っているため、プロセスの次のステップをスキップできます。
AST をバイトコードにコンパイルする
次のステップでは、AST を Python バイトコードにコンパイルします。コンパイルの中間結果は CFG (コントロール フロー グラフ) ですが、同じコードで処理されるため、この詳細は今のところ無視し、別の記事に残しておきます。
次に見るコードは次のとおりです Python/compile.c
. 。の先導に従って while
, 、関数を見つけます compiler_visit_stmt
, 、ステートメントをバイトコードにコンパイルする役割を果たします。という条項を追加します。 Until
:
case While_kind:
return compiler_while(c, s);
case Until_kind:
return compiler_until(c, s);
何かと思ったら Until_kind
つまり、それは定数です (実際には _stmt_kind
列挙型) AST 定義ファイルから自動的に生成され、 Include/Python-ast.h
. 。とにかく、私たちは電話します compiler_until
もちろん、それはまだ存在しません。ちょっと取り掛かります。
私と同じように興味がある人なら気づくでしょう compiler_visit_stmt
独特だ。いくらでも grep
ソースツリーに -ping を実行すると、どこで呼び出されているかがわかります。この場合、残るオプションは C マクロ fu の 1 つだけです。実際、短い調査で次のことがわかります。 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;
}
告白したいことがあります。このコードは、Python バイトコードの深い理解に基づいて書かれていません。記事の残りの部分と同様、これは親族を模倣して行われました。 compiler_while
関数。ただし、Python VM がスタックベースであることを念頭に置き、注意深く読んで、そのドキュメントに目を通します。 dis
モジュールには、 Python バイトコードのリスト 説明があれば、何が起こっているのかを理解することができます。
それで終わりです...そうじゃないですか?
すべての変更を加えて実行した後、 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 番です。条件が true の場合、ループの後にジャンプします。これは正しいセマンティクスです 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'
うわあ...これは良くありません。それで、何が間違っていたのでしょうか?
シンボルテーブルが欠落している場合
Python コンパイラーが AST をコンパイルするときに実行するステップの 1 つは、コンパイルするコードのシンボル テーブルを作成することです。への呼び出し 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 の内部、特にそのフロントエンドに非常に興味があります。したがって、この演習は、コンパイラーの原理とソース コードの理論的研究に非常に役立つものであることがわかりました。これは、コンパイラについてさらに詳しく説明する将来の記事のベースとして機能します。
参考文献
この記事の作成にはいくつかの優れた参考文献を使用しました。以下にそれらを順不同で示します。
- PEP 339:CPython コンパイラの設計 - おそらく最も重要かつ包括的な部分 正式 Python コンパイラのドキュメント。非常に短いので、Python の内部についての適切なドキュメントが不足していることが痛いほどわかります。
- 「Python コンパイラの内部」 - Thomas Lee による記事
- 「パイソン:「設計と実装」 - Guido van Rossum によるプレゼンテーション
- Python (2.5) 仮想マシン、ガイド付きツアー - Peter Tröger によるプレゼンテーション
他のヒント
このようなことを行う1つの方法は、ソースを前処理して変更し、追加したステートメントをpythonに変換することです。このアプローチがもたらすさまざまな問題があり、一般的な使用にはお勧めしませんが、言語の実験や特定の目的のメタプログラミングには、時々役立つことがあります。
たとえば、" myprint"を導入したいとしましょう。ステートメント。画面に出力する代わりに、特定のファイルにログを記録します。例:
myprint "This gets logged to file"
と同等になります
print >>open('/tmp/logfile.txt','a'), "This gets logged to file"
既存のpythonと構文がどれだけ一致するかに応じて、正規表現の置換からASTの生成、独自のパーサーの作成まで、置換の方法に関してさまざまなオプションがあります。適切な中間アプローチはトークナイザーモジュールを使用することです。これにより、Pythonインタープリターと同様にソースを解釈しながら、新しいキーワード、制御構造などを追加できるようになり、粗い正規表現ソリューションの破損を回避できます。上記の「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からコードを使用できるようにする方法です。 1つの方法は、独自のインポート関数を作成し、それを使用してカスタム言語で記述されたコードをロードすることです。例:
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 ) this レシピが示しています。これを次のように実装できます。
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が認識するのは、前処理されたファイルのみです。つまり、スタックトレースなどに出力されるテキストはそれを参照します。重要な翻訳を行った場合、これはソーステキストとは大きく異なる場合があります。上記の例では行番号などは変更されませんので、それほど違いはありませんが、変更するほどわかりにくくなります。
はい、ある程度可能です。 sys.settrace()
を使用してを実装する module があります> goto
および 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"
ソースコードの変更と再コンパイルの短い(オープンソースでは 可能)、ベース言語の変更は実際には不可能です。
ソースを再コンパイルしても、それはpythonではなく、ハックアップされた変更されたバージョンであり、バグを導入しないように非常に注意する必要があります。
しかし、なぜあなたが望むのかはわかりません。 Pythonのオブジェクト指向機能により、現状の言語で同様の結果を得ることが非常に簡単になります。
一般的な答え:ソースファイルを前処理する必要があります。
より具体的な回答: EasyExtend をインストールし、次の手順を実行します
i)新しいラングレット(拡張言語)を作成します
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はいくつかのバグを除けば非常に信頼できます。そのため、プログラマが便利なオブジェクト指向プログラミングを使用して、中置演算子や小さなステートメントなどの便利なものを定義できるAPIが登場するのは時間の問題です。ラングレットを構築してPythonに言語全体を埋め込むなど、より複雑なものについては、完全な文法アプローチを回避する方法はありません。
新しい文を追加する非常に簡単ですが、簡単な方法は、解釈モードのみです。 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
新しいステートメントの追加に関するガイドを見つけました:
https://troeger.eu/files/teaching/pythonvm08lab.pdf
基本的に、新しいステートメントを追加するには、 Python / ast.c
(特に)を編集し、Pythonバイナリを再コンパイルする必要があります。
可能ですが、できません。関数とクラスを介してほとんどすべてを達成できます(スクリプトを実行するためだけにPythonを再コンパイルする必要はありません。)
EasyExtend を使用してこれを行うことができます:
>EasyExtend(EE)はプリプロセッサです ジェネレーターとメタプログラミング 純粋なPythonで書かれたフレームワークと CPythonと統合されています。メイン EasyExtendの目的は作成です 拡張言語の追加 Pythonのカスタム構文とセマンティクス。
インタープリターを変更せずに。過去数年間で多くの言語が「拡張可能」と説明されてきましたが、あなたが説明している方法ではありません。関数とクラスを追加してPythonを拡張します。
Logix と呼ばれるPythonベースの言語があります。しばらくの間開発されていませんが、最新バージョンで機能するを求めた機能。
デコレータを使用していくつかのことができます。例えば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.
およびデコレータは通常、関数を実行するのではなく変更することが期待されています。
以前は、いくつかの機能の作業ディレクトリを一時的に設定する必要があったスクリプトで、このような方法を使用していました。
新しい構文を言語構文に正確に追加するわけではありませんが、マクロは強力なツールです: https:// github。 com / lihaoyi / macropy
10年前にはできませんでしたが、変更されたとは思いません。ただし、Pythonを再コンパイルする準備ができていれば、当時の構文を変更するのはそれほど難しくありませんでしたし、変更されたことも疑っています。