引数クリニックハウツー
- 著者
- ラリーヘイスティングス
概要
引数クリニックは、CPythonCファイルのプリプロセッサです。 その目的は、「ビルトイン」の引数解析コードの記述に関連するすべての定型文を自動化することです。 このドキュメントでは、最初のC関数を引数クリニックで機能するように変換する方法を示し、次に引数クリニックの使用法に関するいくつかの高度なトピックを紹介します。
現在、引数クリニックはCPythonの内部専用と見なされています。 CPython以外のファイルではその使用はサポートされておらず、将来のバージョンの下位互換性については保証されません。 言い換えると、CPythonの外部C拡張機能を維持している場合は、独自のコードで引数クリニックを試してみることを歓迎します。 ただし、次のバージョンのCPython に同梱されているArgumentClinicのバージョンは完全に互換性がなく、すべてのコードが破損する可能性があります。
議論クリニックの目標
引数クリニックの主な目標は、CPython内のすべての引数解析コードの責任を引き継ぐことです。 つまり、関数を引数クリニックで動作するように変換すると、その関数は独自の引数解析を実行しなくなります。引数クリニックによって生成されたコードは、CPythonがで呼び出す「ブラックボックス」である必要があります。上に、コードが下に呼び出され、PyObject *args
(およびおそらくPyObject *kwargs
)が必要なC変数と型に魔法のように変換されます。
引数クリニックがその主要な目標を達成するためには、それが使いやすいものでなければなりません。 現在、CPythonの引数解析ライブラリを操作するのは面倒であり、驚くほど多くの場所で冗長な情報を維持する必要があります。 引数クリニックを使用する場合は、繰り返す必要はありません。
明らかに、問題を解決しない限り、そして独自の新しい問題を作成せずに、引数クリニックを使用したいと思う人はいないでしょう。 したがって、引数クリニックが正しいコードを生成することが最も重要です。 コードの方が速いといいのですが、少なくとも、大幅な速度の低下は起こらないはずです。 (最終的には、引数クリニックは大幅な高速化を可能にするはずです。汎用のCPython引数解析ライブラリを呼び出すのではなく、コードジェネレーターを書き直して、カスタマイズされた引数解析コードを生成できます。 これにより、可能な限り最速の引数解析が可能になります!)
さらに、引数クリニックは、引数の構文解析へのあらゆるアプローチで機能するのに十分な柔軟性を備えている必要があります。 Pythonには、非常に奇妙な解析動作を伴う関数がいくつかあります。 引数クリニックの目標は、それらすべてをサポートすることです。
最後に、Argument Clinicの元々の動機は、CPythonビルトインのイントロスペクション「署名」を提供することでした。 以前は、ビルトインを渡した場合、イントロスペクションクエリ関数は例外をスローしていました。 引数クリニックでは、それは過去のものです!
引数クリニックで作業するときは、覚えておくべき1つのアイデアがあります。それは、提供する情報が多ければ多いほど、より良い仕事ができるようになるということです。 議論クリニックは確かに今のところ比較的単純です。 しかし、それが進化するにつれて、それはより洗練され、あなたがそれを与えるすべての情報で多くの面白くて賢いことをすることができるはずです。
基本的な概念と使用法
引数クリニックはCPythonに付属しています。 Tools/clinic/clinic.py
にあります。 そのスクリプトを実行する場合、引数としてCファイルを指定します。
$ python3 Tools/clinic/clinic.py foo.c
引数クリニックはファイルをスキャンして、次のような行を探します。
/*[clinic input]
見つかった場合は、次のような行まですべてを読み取ります。
[clinic start generated code]*/
これらの2行の間にあるものはすべて、引数クリニックに入力されます。 開始コメント行と終了コメント行を含むこれらの行はすべて、まとめて引数クリニックの「ブロック」と呼ばれます。
Argument Clinicがこれらのブロックの1つを解析すると、出力が生成されます。 この出力は、ブロックの直後にCファイルに書き換えられ、その後にチェックサムを含むコメントが続きます。 ArgumentClinicブロックは次のようになります。
/*[clinic input]
... clinic input goes here ...
[clinic start generated code]*/
... clinic output goes here ...
/*[clinic end generated code: checksum=...]*/
同じファイルでもう一度ArgumentClinicを実行すると、Argument Clinicは古い出力を破棄し、新しいチェックサム行を使用して新しい出力を書き出します。 ただし、入力が変更されていない場合、出力も変更されません。
ArgumentClinicブロックの出力部分は絶対に変更しないでください。 代わりに、必要な出力が生成されるまで入力を変更してください。 (これがチェックサムの目的です。次に引数クリニックが新しい出力を書き出すときにこれらの編集が失われるため、誰かが出力を変更したかどうかを検出します。)
わかりやすくするために、引数クリニックで使用する用語は次のとおりです。
- コメントの最初の行(
/*[clinic input]
)は、開始行です。 - 最初のコメントの最後の行(
[clinic start generated code]*/
)は、終了行です。 - 最後の行(
/*[clinic end generated code: checksum=...]*/
)は、チェックサム行です。 - スタートラインとエンドラインの間には入力があります。
- エンドラインとチェックサムラインの間には、出力があります。
- 開始行からチェックサム行までのすべてのテキストは、まとめてブロックです。 (引数クリニックによって正常に処理されていないブロックには、出力またはチェックサム行がありませんが、それでもブロックと見なされます。)
最初の関数の変換
引数クリニックがどのように機能するかを理解するための最良の方法は、関数をそれと連動するように変換することです。 ここで、引数クリニックで機能するように関数を変換するために従う必要のある最低限の手順を示します。 CPythonにチェックインする予定のコードの場合は、ドキュメントの後半で説明する高度な概念(「リターンコンバーター」や「セルフコンバーター」など)を使用して、変換をさらに進める必要があることに注意してください。 ただし、このウォークスルーでは、学習できるようにシンプルにしています。
飛び込みましょう!
CPythonトランクの新しく更新されたチェックアウトで作業していることを確認してください。
PyArg_ParseTuple()または PyArg_ParseTupleAndKeywords()のいずれかを呼び出し、引数クリニックで動作するようにまだ変換されていないPythonビルトインを見つけます。 私の例では、
_pickle.Pickler.dump()
を使用しています。PyArg_Parse
関数の呼び出しで、次の形式単位のいずれかが使用されている場合:O& O! es es# et et#
または、 PyArg_ParseTuple()への呼び出しが複数ある場合は、別の関数を選択する必要があります。 引数クリニックはこれらすべてのシナリオをサポートします。 ただし、これらは高度なトピックです。最初の関数について、もっと簡単なことをしてみましょう。
また、関数に PyArg_ParseTuple()または PyArg_ParseTupleAndKeywords()への複数の呼び出しがある場合、または関数がPyArg_Parse関数以外のものを使用してその引数を解析する場合引数、それはおそらく引数クリニックへの変換には適していません。 引数クリニックは、総称関数または多形パラメーターをサポートしていません。
関数の上に次の定型文を追加して、ブロックを作成します。
/*[clinic input] [clinic start generated code]*/
docstringを切り取り、
[clinic]
行の間に貼り付けて、適切に引用されたC文字列になるすべてのジャンクを削除します。 完了したら、左マージンに基づいて、80文字を超える行がないテキストだけを作成する必要があります。 (引数クリニックは、docstring内のインデントを保持します。)古いdocstringに関数シグネチャのように見える最初の行があった場合は、その行を破棄します。 (docstringはもう必要ありません。将来ビルトインで
help()
を使用すると、最初の行は関数のシグネチャに基づいて自動的に作成されます。)サンプル:
/*[clinic input] Write a pickled representation of obj to the open file. [clinic start generated code]*/
docstringに「summary」行がない場合、ArgumentClinicは文句を言います。 それで、それが1つあることを確認しましょう。 「要約」行は、docstringの先頭にある単一の80列の行で構成される段落である必要があります。
(この例のdocstringは要約行のみで構成されているため、このステップでサンプルコードを変更する必要はありません。)
docstringの上に、関数の名前を入力し、その後に空白行を入力します。 これは関数のPython名であり、関数への完全な点線のパスである必要があります。モジュールの名前で始まり、サブモジュールを含める必要があります。関数がクラスのメソッドである場合は、含める必要があります。クラス名も。
サンプル:
/*[clinic input] _pickle.Pickler.dump Write a pickled representation of obj to the open file. [clinic start generated code]*/
このCファイルでモジュールまたはクラスが引数クリニックで使用されるのが初めての場合は、モジュールまたはクラス、あるいはその両方を宣言する必要があります。 適切な引数クリニックの衛生状態は、ファイルと統計を含むものが上部に配置されるのと同じ方法で、Cファイルの上部近くの別のブロックでこれらを宣言することを好みます。 (サンプルコードでは、2つのブロックを並べて表示します。)
クラスとモジュールの名前は、Pythonで見られるものと同じである必要があります。 PyModuleDef または PyTypeObject で定義されている名前を適宜確認してください。
クラスを宣言するときは、Cでその型の2つの側面も指定する必要があります。このクラスのインスタンスへのポインターに使用する型宣言と、このクラスの PyTypeObject へのポインターです。 。
サンプル:
/*[clinic input] module _pickle class _pickle.Pickler "PicklerObject *" "&Pickler_Type" [clinic start generated code]*/ /*[clinic input] _pickle.Pickler.dump Write a pickled representation of obj to the open file. [clinic start generated code]*/
関数の各パラメーターを宣言します。 各パラメータは独自の行を取得する必要があります。 すべてのパラメーター行は、関数名とdocstringからインデントする必要があります。
これらのパラメータ行の一般的な形式は次のとおりです。
name_of_parameter: converter
パラメータにデフォルト値がある場合は、コンバータの後に追加します。
name_of_parameter: converter = default_value
引数クリニックの「デフォルト値」のサポートは非常に洗練されています。 詳細については、デフォルト値に関する以下のセクションを参照してください。
パラメータの下に空白行を追加します。
「コンバーター」とは何ですか? これは、Cで使用される変数のタイプと、実行時にPython値をC値に変換するメソッドの両方を確立します。 ここでは、「レガシーコンバーター」と呼ばれるものを使用します。これは、古いコードを引数クリニックに簡単に移植できるようにすることを目的とした便利な構文です。
各パラメーターについて、そのパラメーターの「フォーマット単位」を
PyArg_Parse()
フォーマット引数からコピーし、 that をコンバーターとして引用符で囲んだ文字列として指定します。 (「フォーマット単位」は、format
パラメーターの1〜3文字の部分文字列の正式な名前であり、変数のタイプとその変換方法を引数解析関数に指示します。 フォーマット単位の詳細については、引数の解析と値の構築を参照してください。)z#
のような複数文字形式の単位の場合は、2文字または3文字の文字列全体を使用します。サンプル:
/*[clinic input] module _pickle class _pickle.Pickler "PicklerObject *" "&Pickler_Type" [clinic start generated code]*/ /*[clinic input] _pickle.Pickler.dump obj: 'O' Write a pickled representation of obj to the open file. [clinic start generated code]*/
関数のフォーマット文字列に
|
が含まれている場合、つまり一部のパラメーターにデフォルト値がある場合は、無視してかまいません。 引数クリニックは、デフォルト値があるかどうかに基づいて、どのパラメーターがオプションであるかを推測します。関数のフォーマット文字列に
$
が含まれている場合、つまりキーワードのみの引数を取る場合は、最初のキーワードのみの引数の前の行で*
を指定し、パラメーター行と同じようにインデントします。 。(
_pickle.Pickler.dump
にはどちらも含まれていないため、サンプルは変更されていません。)既存のC関数が( PyArg_ParseTupleAndKeywords()ではなく) PyArg_ParseTuple()を呼び出す場合、そのすべての引数は位置のみです。
引数クリニックですべてのパラメーターを位置のみとしてマークするには、最後のパラメーターの後に、パラメーター行と同じようにインデントされた行に
/
を追加します。現在、これはオールオアナッシングです。 すべてのパラメーターが位置のみであるか、いずれもそうではありません。 (将来、引数クリニックはこの制限を緩和する可能性があります。)
サンプル:
/*[clinic input] module _pickle class _pickle.Pickler "PicklerObject *" "&Pickler_Type" [clinic start generated code]*/ /*[clinic input] _pickle.Pickler.dump obj: 'O' / Write a pickled representation of obj to the open file. [clinic start generated code]*/
パラメータごとにパラメータごとのdocstringを作成すると便利です。 ただし、パラメータごとのdocstringはオプションです。 必要に応じて、この手順をスキップできます。
パラメータごとのdocstringを追加する方法は次のとおりです。 パラメータごとのdocstringの最初の行は、パラメータ定義よりもさらにインデントする必要があります。 この最初の行の左マージンは、パラメーターごとのdocstring全体の左マージンを確立します。 あなたが書くすべてのテキストは、この量だけアウトデントされます。 必要に応じて、複数の行にまたがって、好きなだけテキストを書くことができます。
サンプル:
/*[clinic input] module _pickle class _pickle.Pickler "PicklerObject *" "&Pickler_Type" [clinic start generated code]*/ /*[clinic input] _pickle.Pickler.dump obj: 'O' The object to be pickled. / Write a pickled representation of obj to the open file. [clinic start generated code]*/
ファイルを保存して閉じ、
Tools/clinic/clinic.py
を実行します。 運が良ければ、すべてが機能しました。ブロックに出力があり、.c.h
ファイルが生成されました。 テキストエディタでファイルを再度開いて、次を確認します。/*[clinic input] _pickle.Pickler.dump obj: 'O' The object to be pickled. / Write a pickled representation of obj to the open file. [clinic start generated code]*/ static PyObject * _pickle_Pickler_dump(PicklerObject *self, PyObject *obj) /*[clinic end generated code: output=87ecad1261e02ac7 input=552eb1c0f52260d9]*/
明らかに、引数クリニックが出力を生成しなかった場合、それは入力にエラーが見つかったためです。 Argument Clinicが苦情なしにファイルを処理するまで、エラーを修正して再試行し続けます。
読みやすくするために、ほとんどのグルーコードは
.c.h
ファイルに生成されています。 これを元の.c
ファイルに含める必要があります。通常は、クリニックモジュールブロックの直後です。#include "clinic/_pickle.c.h"
生成された引数解析コードArgumentClinicが基本的に既存のコードと同じに見えることを再確認します。
まず、両方の場所で同じ引数解析関数が使用されていることを確認します。 既存のコードは、 PyArg_ParseTuple()または PyArg_ParseTupleAndKeywords()のいずれかを呼び出す必要があります。 引数クリニックによって生成されたコードが exact 同じ関数を呼び出すことを確認してください。
次に、 PyArg_ParseTuple()または PyArg_ParseTupleAndKeywords()に渡されるフォーマット文字列は、既存の関数の手書きのものと[X119X]正確に同じである必要があります。コロンまたはセミコロンに。
(引数クリニックは常に
:
の後に関数の名前が続くフォーマット文字列を生成します。 既存のコードのフォーマット文字列が;
で終わっている場合、使用法のヘルプを提供するために、この変更は無害です。心配しないでください。)第3に、フォーマット単位に2つの引数(長さ変数、エンコード文字列、変換関数へのポインターなど)が必要なパラメーターの場合、2番目の引数が2つの呼び出し間で正確に同じであることを確認します。
第4に、ブロックの出力部分の中に、このビルトインに適切な静的 PyMethodDef 構造を定義するプリプロセッサマクロがあります。
#define __PICKLE_PICKLER_DUMP_METHODDEF \ {"dump", (PyCFunction)__pickle_Pickler_dump, METH_O, __pickle_Pickler_dump__doc__},
この静的構造は、このビルトインの既存の静的 PyMethodDef 構造と正確に同じである必要があります。
これらの項目のいずれかが何らかの方法で異なる場合は、引数クリニック関数の仕様を調整し、が同じになるまで
Tools/clinic/clinic.py
を再実行します。その出力の最後の行が「impl」関数の宣言であることに注意してください。 これは、ビルトインの実装が進むところです。 変更する関数の既存のプロトタイプを削除しますが、最初の中括弧はそのままにしておきます。 次に、引数解析コードと、引数をダンプするすべての変数の宣言を削除します。 Pythonの引数がこのimpl関数の引数になっていることに注目してください。 実装でこれらの変数に異なる名前が使用されている場合は、修正してください。
ちょっと変だからといって、繰り返してみましょう。 これで、コードは次のようになります。
static return_type your_function_impl(...) /*[clinic end generated code: checksum=...]*/ { ...
引数クリニックは、チェックサム行とそのすぐ上にある関数プロトタイプを生成しました。 関数の開始(および終了)中括弧とその内部の実装を記述する必要があります。
サンプル:
/*[clinic input] module _pickle class _pickle.Pickler "PicklerObject *" "&Pickler_Type" [clinic start generated code]*/ /*[clinic end generated code: checksum=da39a3ee5e6b4b0d3255bfef95601890afd80709]*/ /*[clinic input] _pickle.Pickler.dump obj: 'O' The object to be pickled. / Write a pickled representation of obj to the open file. [clinic start generated code]*/ PyDoc_STRVAR(__pickle_Pickler_dump__doc__, "Write a pickled representation of obj to the open file.\n" "\n" ... static PyObject * _pickle_Pickler_dump_impl(PicklerObject *self, PyObject *obj) /*[clinic end generated code: checksum=3bd30745bf206a48f8b576a1da3d90f55a0a4187]*/ { /* Check whether the Pickler was initialized correctly (issue3664). Developers often forget to call __init__() in their subclasses, which would trigger a segfault without this check. */ if (self->write == NULL) { PyErr_Format(PicklingError, "Pickler.__init__() was not called by %s.__init__()", Py_TYPE(self)->tp_name); return NULL; } if (_Pickler_ClearBuffer(self) < 0) return NULL; ...
この関数の PyMethodDef 構造を持つマクロを覚えていますか? この関数の既存の PyMethodDef 構造を見つけて、マクロへの参照に置き換えます。 (ビルトインがモジュールスコープにある場合、これはおそらくファイルの終わりに非常に近いでしょう。ビルトインがクラスメソッドである場合、これはおそらく実装の下にありますが、比較的近いでしょう。)
マクロの本体には末尾にコンマが含まれていることに注意してください。 したがって、既存の静的 PyMethodDef 構造をマクロに置き換える場合、最後にコンマを追加しないでください。
サンプル:
static struct PyMethodDef Pickler_methods[] = { __PICKLE_PICKLER_DUMP_METHODDEF __PICKLE_PICKLER_CLEAR_MEMO_METHODDEF {NULL, NULL} /* sentinel */ };
コンパイルしてから、回帰テストスイートの関連部分を実行します。 この変更により、コンパイル時の新しい警告やエラーが発生したり、Pythonの動作に外部から見える変更があったりすることはありません。
1つの違いを除いて、関数で
inspect.signature()
を実行すると、有効な署名が提供されるはずです。おめでとうございます。引数クリニックで動作する最初の関数を移植しました。
高度なトピック
Argument Clinicでの作業経験があったので、次はいくつかの高度なトピックについて説明します。
シンボリックデフォルト値
パラメータに指定するデフォルト値は、任意の式にすることはできません。 現在、以下が明示的にサポートされています。
- 数値定数(整数と浮動小数点)
- 文字列定数
True
、False
、およびNone
sys.maxsize
のような単純なシンボリック定数。これは、モジュールの名前で始まる必要があります。
気になる方のために、これはLib/inspect.py
のfrom_builtin()
に実装されています。
(将来的には、CONSTANT - 1
のような完全な表現を可能にするために、これをさらに複雑にする必要があるかもしれません。)
引数クリニックによって生成されたC関数と変数の名前を変更する
引数クリニックは、生成する関数に自動的に名前を付けます。 生成された名前が既存のC関数の名前と衝突する場合、これが問題を引き起こすことがあります。 簡単な解決策があります。C関数に使用される名前をオーバーライドします。 キーワード"as"
を関数宣言行に追加し、その後に使用する関数名を追加するだけです。 引数クリニックは、その関数名を基本(生成された)関数に使用し、最後に"_impl"
を追加して、それをimpl関数の名前に使用します。
たとえば、pickle.Pickler.dump
用に生成されたC関数名の名前を変更する場合、次のようになります。
/*[clinic input]
pickle.Pickler.dump as pickler_dumper
...
基本関数の名前はpickler_dumper()
になり、impl関数の名前はpickler_dumper_impl()
になります。
同様に、パラメータに特定のPython名を付けたい場合に問題が発生する可能性がありますが、その名前はCでは不便な場合があります。 引数クリニックでは、同じ"as"
構文を使用して、PythonとCでパラメーターに異なる名前を付けることができます。
/*[clinic input]
pickle.Pickler.dump
obj: object
file as file_obj: object
protocol: object = NULL
*
fix_imports: bool = True
ここで、Pythonで(署名とkeywords
配列で)使用される名前はfile
ですが、C変数の名前はfile_obj
になります。
これを使用して、self
パラメーターの名前を変更することもできます。
PyArg_UnpackTupleを使用した関数の変換
PyArg_UnpackTuple()を使用して引数を解析する関数を変換するには、すべての引数をobject
として指定して書き出すだけです。 type
引数を指定して、必要に応じて型をキャストできます。 すべての引数は位置のみとしてマークする必要があります(最後の引数の後に/
を単独で行に追加します)。
現在、生成されたコードは PyArg_ParseTuple()を使用しますが、これはまもなく変更されます。
オプションのグループ
一部のレガシー関数には、引数を解析するためのトリッキーなアプローチがあります。位置引数の数をカウントし、switch
ステートメントを使用して、数に応じていくつかの異なる PyArg_ParseTuple()呼び出しの1つを呼び出します。位置引数があります。 (これらの関数はキーワードのみの引数を受け入れることはできません。)このアプローチは、 PyArg_ParseTupleAndKeywords()が作成される前にオプションの引数をシミュレートするために使用されました。
このアプローチを使用する関数は、多くの場合、 PyArg_ParseTupleAndKeywords()、オプションの引数、およびデフォルト値を使用するように変換できますが、常に可能であるとは限りません。 これらのレガシー関数の一部には、 PyArg_ParseTupleAndKeywords()が直接サポートしていない動作があります。 最も明白な例は、組み込み関数range()
です。これには、必須の引数の左側側にオプションの引数があります。 もう1つの例は、curses.window.addch()
です。これには、常に一緒に指定する必要がある2つの引数のグループがあります。 (引数はx
およびy
と呼ばれます。x
を渡す関数を呼び出す場合は、y
も渡す必要があります。そうでない場合は、 x
を渡すと、y
も渡すことができません。)
いずれにせよ、引数クリニックの目標は、セマンティクスを変更せずに、既存のすべてのCPythonビルトインの引数解析をサポートすることです。 したがって、引数クリニックは、オプショングループと呼ばれるものを使用して、構文解析に対するこの代替アプローチをサポートします。 オプションのグループは、すべて一緒に渡す必要がある引数のグループです。 それらは、必要な引数の左側または右側に配置できます。 のみは、位置のみのパラメーターで使用できます。
ノート
オプションのグループは、 PyArg_ParseTuple()を複数回呼び出す関数を変換するときに使用することを目的としたのみです。 引数を解析するために任意の他のアプローチを使用する関数は、オプションのグループを使用してほとんど引数クリニックに変換されるべきではありません。 Pythonは概念を理解していないため、オプションのグループを使用する関数は現在、Pythonで正確な署名を持つことができません。 可能な限り、オプションのグループの使用は避けてください。
オプションのグループを指定するには、グループ化するパラメーターの前に[
を単独で追加し、これらのパラメーターの後に]
を単独で追加します。 例として、curses.window.addch
がオプションのグループを使用して、最初の2つのパラメーターと最後のパラメーターをオプションにする方法を次に示します。
/*[clinic input]
curses.window.addch
[
x: int
X-coordinate.
y: int
Y-coordinate.
]
ch: object
Character to add.
[
attr: long
Attributes for the character.
]
/
...
ノート:
- オプションのグループごとに、グループを表すimpl関数に1つの追加パラメーターが渡されます。 パラメータは
group_{direction}_{number}
という名前のintになります。ここで、{direction}
は、グループが必要なパラメータの前か後かに応じて、right
またはleft
のいずれかになります。{number}
は、単調に増加する数値(1から開始)であり、グループが必要なパラメーターからどれだけ離れているかを示します。 implが呼び出されると、このパラメーターは、このグループが使用されていない場合はゼロに設定され、このグループが使用されている場合はゼロ以外に設定されます。 (使用済みまたは未使用とは、パラメーターがこの呼び出しで引数を受け取ったかどうかを意味します。) - 必須の引数がない場合、オプションのグループは、必須の引数の右側にあるかのように動作します。
- あいまいな場合、引数解析コードは左側のパラメーター(必要なパラメーターの前)を優先します。
- オプションのグループには、位置のみのパラメーターのみを含めることができます。
- オプションのグループは、レガシーコードを対象としたのみです。 新しいコードにオプションのグループを使用しないでください。
「レガシーコンバーター」の代わりに、実際の引数クリニックコンバーターを使用する
時間を節約し、引数クリニックへの最初の移植を達成するために学ぶ必要のある量を最小限に抑えるために、上記のウォークスルーでは「レガシーコンバーター」を使用するように指示しています。 「レガシーコンバーター」は便利で、既存のコードを引数クリニックに簡単に移植できるように明示的に設計されています。 そして明確にするために、Python3.4のコードを移植するときにそれらの使用は許容されます。
ただし、長期的には、すべてのブロックでArgumentClinicの実際の構文をコンバーターに使用する必要があります。 どうして? いくつかの理由:
- 適切なコンバーターは、はるかに読みやすく、意図が明確です。
- 引数が必要であり、レガシーコンバータ構文が引数の指定をサポートしていないため、「レガシーコンバータ」としてサポートされていないフォーマット単位がいくつかあります。
- 将来的には、 PyArg_ParseTuple()がサポートするものに制限されない新しい引数解析ライブラリが登場する可能性があります。 この柔軟性は、レガシーコンバーターを使用するパラメーターでは使用できません。
したがって、少し余分な労力を気にしない場合は、レガシーコンバーターの代わりに通常のコンバーターを使用してください。
一言で言えば、引数クリニック(非レガシー)コンバーターの構文はPython関数呼び出しのように見えます。 ただし、関数に明示的な引数がない場合(すべての関数はデフォルト値を取ります)、括弧を省略できます。 したがって、bool
とbool()
はまったく同じコンバーターです。
引数クリニックコンバーターへのすべての引数はキーワードのみです。 すべての引数クリニックコンバーターは、次の引数を受け入れます。
c_default
- Cで定義されている場合のこのパラメーターのデフォルト値。 具体的には、これは「解析関数」で宣言された変数の初期化子になります。 使用方法については、デフォルト値のセクションを参照してください。 文字列として指定されます。
annotation
- このパラメーターの注釈値。 PEP 8 では、Pythonライブラリで注釈を使用できないことが義務付けられているため、現在はサポートされていません。
さらに、一部のコンバーターは追加の引数を受け入れます。 これらの引数とその意味のリストを次に示します。
accept
Pythonタイプのセット(および場合によっては疑似タイプ)。 これにより、許容されるPython引数がこれらのタイプの値に制限されます。 (これは汎用機能ではありません。原則として、レガシーコンバーターの表に示されている特定のタイプのリストのみをサポートします。)
None
を受け入れるには、このセットにNoneType
を追加します。bitwise
符号なし整数でのみサポートされます。 このPython引数のネイティブ整数値は、負の値であっても、範囲チェックなしでパラメーターに書き込まれます。
converter
object
コンバーターでのみサポートされます。 このオブジェクトをネイティブ型に変換するために使用する C「コンバーター関数」の名前を指定します。encoding
文字列でのみサポートされます。 この文字列をPythonstr(Unicode)値からC
char *
値に変換するときに使用するエンコードを指定します。subclass_of
object
コンバーターでのみサポートされます。 Cで表されるように、Python値がPython型のサブクラスである必要があります。type
object
およびself
コンバーターでのみサポートされます。 変数の宣言に使用されるCタイプを指定します。 デフォルト値は"PyObject *"
です。zeroes
文字列でのみサポートされます。 trueの場合、埋め込みNULバイト(
'\\0'
)が値内で許可されます。 文字列の長さは、文字列パラメータの直後に、<parameter_name>_length
という名前のパラメータとしてimpl関数に渡されます。
引数の可能なすべての組み合わせが機能するとは限らないことに注意してください。 通常、これらの引数は、特定の動作を伴う特定のPyArg_ParseTuple
フォーマットユニットによって実装されます。 たとえば、現在、bitwise=True
も指定せずにunsigned_short
を呼び出すことはできません。 これが機能すると考えるのは完全に合理的ですが、これらのセマンティクスは既存のフォーマット単位にマップされません。 したがって、引数クリニックはそれをサポートしていません。 (または、少なくとも、まだです。)
以下は、レガシーコンバーターの実際の引数クリニックコンバーターへのマッピングを示す表です。 左側はレガシーコンバーターで、右側はそれを置き換えるテキストです。
'B'
|
unsigned_char(bitwise=True)
|
'b'
|
unsigned_char
|
'c'
|
char
|
'C'
|
int(accept={str})
|
'd'
|
double
|
'D'
|
Py_complex
|
'es'
|
str(encoding='name_of_encoding')
|
'es#'
|
str(encoding='name_of_encoding', zeroes=True)
|
'et'
|
str(encoding='name_of_encoding', accept={bytes, bytearray, str})
|
'et#'
|
str(encoding='name_of_encoding', accept={bytes, bytearray, str}, zeroes=True)
|
'f'
|
float
|
'h'
|
short
|
'H'
|
unsigned_short(bitwise=True)
|
'i'
|
int
|
'I'
|
unsigned_int(bitwise=True)
|
'k'
|
unsigned_long(bitwise=True)
|
'K'
|
unsigned_long_long(bitwise=True)
|
'l'
|
long
|
'L'
|
long long
|
'n'
|
Py_ssize_t
|
'O'
|
object
|
'O!'
|
object(subclass_of='&PySomething_Type')
|
'O&'
|
object(converter='name_of_c_function')
|
'p'
|
bool
|
'S'
|
PyBytesObject
|
's'
|
str
|
's#'
|
str(zeroes=True)
|
's*'
|
Py_buffer(accept={buffer, str})
|
'U'
|
unicode
|
'u'
|
Py_UNICODE
|
'u#'
|
Py_UNICODE(zeroes=True)
|
'w*'
|
Py_buffer(accept={rwbuffer})
|
'Y'
|
PyByteArrayObject
|
'y'
|
str(accept={bytes})
|
'y#'
|
str(accept={robuffer}, zeroes=True)
|
'y*'
|
Py_buffer
|
'Z'
|
Py_UNICODE(accept={str, NoneType})
|
'Z#'
|
Py_UNICODE(accept={str, NoneType}, zeroes=True)
|
'z'
|
str(accept={str, NoneType})
|
'z#'
|
str(accept={str, NoneType}, zeroes=True)
|
'z*'
|
Py_buffer(accept={buffer, str, NoneType})
|
例として、適切なコンバーターを使用したサンプルpickle.Pickler.dump
を次に示します。
/*[clinic input]
pickle.Pickler.dump
obj: object
The object to be pickled.
/
Write a pickled representation of obj to the open file.
[clinic start generated code]*/
実際のコンバーターの利点の1つは、レガシーコンバーターよりも柔軟性があることです。 たとえば、unsigned_int
コンバーター(およびすべてのunsigned_
コンバーター)は、bitwise=True
なしで指定できます。 デフォルトの動作では、値の範囲チェックが実行され、負の数は受け入れられません。 従来のコンバーターではそれができません!
引数クリニックは、利用可能なすべてのコンバーターを表示します。 コンバーターごとに、受け入れるすべてのパラメーターと、各パラメーターのデフォルト値が表示されます。 Tools/clinic/clinic.py --converters
を実行するだけで、完全なリストが表示されます。
Py_buffer
Py_buffer
コンバーター(または's*'
、'w*'
、'*y'
、または'z*'
レガシーコンバーター)を使用する場合は、が必要です。 は、提供されたバッファーで PyBuffer_Release()を呼び出さないでください。 引数クリニックは、(解析関数で)それを実行するコードを生成します。
高度なコンバーター
高度なために初めてスキップしたフォーマット単位を覚えていますか? これらも処理する方法は次のとおりです。
秘訣は、これらすべてのフォーマット単位が引数(変換関数、型、またはエンコーディングを指定する文字列)を受け取ることです。 (ただし、「レガシーコンバーター」は引数をサポートしていません。 そのため、最初の関数ではそれらをスキップしました。)フォーマット単位に指定した引数は、コンバーターへの引数になりました。 この引数は、converter
(O&
の場合)、subclass_of
(O!
の場合)、またはencoding
( e
で始まります)。
subclass_of
を使用する場合は、object()
の他のカスタム引数type
を使用することもできます。これにより、パラメーターに実際に使用されるタイプを設定できます。 たとえば、オブジェクトがPyUnicode_Type
のサブクラスであることを確認する場合は、コンバーターobject(type='PyUnicodeObject *', subclass_of='&PyUnicode_Type')
を使用することをお勧めします。
引数クリニックを使用する際に考えられる問題の1つは、e
で始まるフォーマット単位の柔軟性が失われることです。 PyArg_Parse
呼び出しを手動で作成する場合、理論的には、実行時に PyArg_ParseTuple()に渡すエンコード文字列を決定できます。 ただし、この文字列はArgument-Clinic-preprocessing-timeでハードコーディングする必要があります。 この制限は意図的なものです。 これにより、このフォーマットユニットのサポートがはるかに簡単になり、将来の最適化が可能になる可能性があります。 この制限は不合理に思えません。 CPython自体は、フォーマット単位がe
で始まるパラメーターの静的なハードコードされたエンコード文字列を常に渡します。
パラメータのデフォルト値
パラメータのデフォルト値は、任意の数の値にすることができます。 最も単純なものは、string、int、またはfloatリテラルです。
foo: str = "abc"
bar: int = 123
bat: float = 45.6
また、Pythonの組み込み定数のいずれかを使用できます。
yep: bool = True
nope: bool = False
nada: object = None
NULL
のデフォルト値、および次のセクションで説明する単純な式の特別なサポートもあります。
NULLのデフォルト値
文字列とオブジェクトのパラメータについては、None
に設定して、デフォルトがないことを示すことができます。 ただし、これは、C変数がPy_None
に初期化されることを意味します。 便宜上、この理由からNULL
という特別な値があります。Pythonの観点からは、デフォルト値のNone
のように動作しますが、C変数はNULL
で初期化されます。 。
デフォルト値として指定された式
パラメータのデフォルト値は、単なるリテラル値以上のものにすることができます。 数学演算子を使用し、オブジェクトの属性を検索して、式全体にすることができます。 ただし、いくつかの非自明なセマンティクスのため、このサポートは正確には単純ではありません。
次の例を考えてみましょう。
foo: Py_ssize_t = sys.maxsize - 1
sys.maxsize
は、プラットフォームごとに異なる値を持つことができます。 したがって、Argument Clinicは、その式をローカルで単純に評価して、Cでハードコーディングすることはできません。 そのため、ユーザーが関数のシグネチャを要求したときに実行時に評価されるようにデフォルトを保存します。
式が評価されるときに使用できる名前空間は何ですか? ビルトインが由来するモジュールのコンテキストで評価されます。 したがって、モジュールに「max_widgets
」という属性がある場合は、単にそれを使用できます。
foo: Py_ssize_t = max_widgets
現在のモジュールでシンボルが見つからない場合は、sys.modules
の検索にフェイルオーバーします。 これが、たとえばsys.maxsize
を見つける方法です。 (ユーザーがインタープリターにロードするモジュールが事前にわからないため、Python自体によってプリロードされるモジュールに制限することをお勧めします。)
実行時にのみデフォルト値を評価するということは、引数クリニックが正しい同等のCデフォルト値を計算できないことを意味します。 したがって、明示的に伝える必要があります。 式を使用する場合は、コンバーターのc_default
パラメーターを使用して、Cで同等の式も指定する必要があります。
foo: Py_ssize_t(c_default="PY_SSIZE_T_MAX - 1") = sys.maxsize - 1
もう1つの複雑な問題:引数クリニックは、指定した式が有効かどうかを事前に知ることができません。 それはそれが合法に見えることを確認するためにそれを解析します、しかしそれは実際に知ることができません。 式を使用して実行時に有効であることが保証されている値を指定する場合は、十分に注意する必要があります。
最後に、式は静的C値として表現可能でなければならないため、正当な式には多くの制限があります。 使用が許可されていないPython機能のリストは次のとおりです。
- 関数呼び出し。
- インラインifステートメント(
3 if foo else 5
)。 - 自動シーケンスアンパック(
*[1, 2, 3]
)。 - リスト内包表記とジェネレータ式。
- タプル/リスト/セット/ dictリテラル。
リターンコンバーターの使用
デフォルトでは、引数クリニックが生成するimpl関数はPyObject *
を返します。 ただし、C関数は多くの場合C型を計算し、最後にそれをPyObject *
に変換します。 引数クリニックは、PythonタイプからネイティブCタイプへの入力の変換を処理します。ネイティブCタイプからPythonタイプへの戻り値も変換しないのはなぜですか。
それが「リターンコンバーター」が行うことです。 impl関数を変更してCタイプを返し、生成された(非impl)関数にコードを追加して、その値を適切なPyObject *
に変換します。
リターンコンバーターの構文は、パラメーターコンバーターの構文と似ています。 関数自体のリターンアノテーションであるかのように、リターンコンバーターを指定します。 リターンコンバーターは、パラメーターコンバーターとほとんど同じように動作します。 それらは引数を取り、引数はすべてキーワードのみであり、デフォルトの引数を変更しない場合は、括弧を省略できます。
(関数に"as"
との両方のリターンコンバーターを使用する場合は、"as"
がリターンコンバーターの前に来る必要があります。)
リターンコンバーターを使用する場合、さらに1つの問題があります。エラーが発生したことをどのように示しますか? 通常、関数は成功の場合は有効な(NULL
以外の)ポインターを返し、失敗の場合はNULL
を返します。 ただし、整数リターンコンバーターを使用する場合は、すべての整数が有効です。 引数クリニックはどのようにしてエラーを検出できますか? その解決策:各リターンコンバーターは、エラーを示す特別な値を暗黙的に探します。 その値を返し、エラーが設定されている場合(PyErr_Occurred()
は真の値を返します)、生成されたコードはエラーを伝播します。 それ以外の場合は、通常のように返す値をエンコードします。
現在、Argument Clinicは、いくつかのリターンコンバーターのみをサポートしています。
bool
int
unsigned int
long
unsigned int
size_t
Py_ssize_t
float
double
DecodeFSDefault
これらはいずれもパラメータを取りません。 最初の3つについては、エラーを示すために-1を返します。 DecodeFSDefault
の場合、戻りタイプはconst char *
です。 エラーを示すためにNULL
ポインタを返します。
(実験的なNoneType
コンバーターもあり、Py_None
の参照カウントをインクリメントしなくても、成功した場合はPy_None
を、失敗した場合はNULL
を返すことができます。 使用する価値があるほど明確になるかどうかはわかりません。)
引数クリニックがサポートするすべてのリターンコンバーターとそのパラメーター(存在する場合)を表示するには、Tools/clinic/clinic.py --converters
を実行して完全なリストを表示します。
既存の関数のクローン作成
似たような機能がたくさんある場合は、クリニックの「クローン」機能を使用できる可能性があります。 既存の関数のクローンを作成するときは、以下を再利用します。
- そのパラメータを含む
- 彼らの名前、
- それらのコンバーター、すべてのパラメーター、
- それらのデフォルト値、
- それらのパラメータごとのdocstring、
- それらの種類(位置のみ、位置またはキーワード、またはキーワードのみ)、および
- そのリターンコンバータ。
元の関数からコピーされていないのは、そのdocstringだけです。 構文を使用すると、新しいdocstringを指定できます。
関数のクローンを作成するための構文は次のとおりです。
/*[clinic input]
module.class.new_function [as c_basename] = module.class.existing_function
Docstring for new_function goes here.
[clinic start generated code]*/
(関数は、異なるモジュールまたはクラスに含めることができます。 両方の関数へのフルパスを使用する必要があることを示すために、サンプルにmodule.class
を記述しました。)
申し訳ありませんが、関数を部分的に複製したり、関数を複製してから変更したりするための構文はありません。 クローン作成は、オールオアナッシングの提案です。
また、クローンを作成する関数は、現在のファイルで事前に定義されている必要があります。
Pythonコードの呼び出し
残りの高度なトピックでは、Cファイル内に存在し、ArgumentClinicのランタイム状態を変更するPythonコードを作成する必要があります。 これは簡単です。Pythonブロックを定義するだけです。
Pythonブロックは、ArgumentClinic関数ブロックとは異なる区切り行を使用します。 これは次のようになります。
/*[python input]
# python code goes here
[python start generated code]*/
Pythonブロック内のすべてのコードは、解析時に実行されます。 ブロック内のstdoutに書き込まれたすべてのテキストは、ブロックの後の「出力」にリダイレクトされます。
例として、静的整数変数をCコードに追加するPythonブロックを次に示します。
/*[python input]
print('static int __ignored_unused_variable__ = 0;')
[python start generated code]*/
static int __ignored_unused_variable__ = 0;
/*[python checksum:...]*/
「セルフコンバーター」の使用
引数クリニックは、デフォルトのコンバーターを使用して、「自己」パラメーターを自動的に追加します。 このパラメーターのtype
は、タイプを宣言したときに指定した「インスタンスへのポインター」に自動的に設定されます。 ただし、Argument Clinicのコンバーターをオーバーライドして、自分で指定することはできます。 独自のself
パラメーターをブロックの最初のパラメーターとして追加し、そのコンバーターがself_converter
またはそのサブクラスのインスタンスであることを確認してください。
ポイントは何ですか? これにより、self
のタイプを上書きしたり、別のデフォルト名を付けたりすることができます。
self
をキャストするカスタムタイプをどのように指定しますか? self
に同じタイプの関数が1つまたは2つしかない場合は、引数クリニックの既存のself
コンバーターを直接使用して、使用するタイプをtype
として渡すことができます。 ]パラメータ:
/*[clinic input]
_pickle.Pickler.dump
self: self(type="PicklerObject *")
obj: object
/
Write a pickled representation of the given object to the open file.
[clinic start generated code]*/
一方、self
に同じタイプを使用する関数が多数ある場合は、self_converter
をサブクラス化して、type
を上書きして、独自のコンバーターを作成することをお勧めします。メンバー:
/*[python input]
class PicklerObject_converter(self_converter):
type = "PicklerObject *"
[python start generated code]*/
/*[clinic input]
_pickle.Pickler.dump
self: PicklerObject
obj: object
/
Write a pickled representation of the given object to the open file.
[clinic start generated code]*/
カスタムコンバーターの作成
前のセクションで示唆したように…あなたはあなた自身のコンバーターを書くことができます! コンバーターは、CConverter
から継承する単純なPythonクラスです。 カスタムコンバーターの主な目的は、O&
形式の単位を使用するパラメーターがある場合です。このパラメーターを解析することは、 PyArg_ParseTuple()「コンバーター関数」を呼び出すことを意味します。
コンバータークラスには*something*_converter
という名前を付ける必要があります。 名前がこの規則に従っている場合、コンバータークラスは自動的に引数クリニックに登録されます。 その名前は、_converter
サフィックスが削除されたクラスの名前になります。 (これはメタクラスで実現されます。)
CConverter.__init__
をサブクラス化しないでください。 代わりに、converter_init()
関数を作成する必要があります。 converter_init()
は常にself
パラメーターを受け入れます。 その後、すべての追加パラメーターはキーワードのみである必要があります。 引数クリニックでコンバーターに渡された引数はすべて、converter_init()
に渡されます。
サブクラスで指定したいCConverter
の追加メンバーがいくつかあります。 現在のリストは次のとおりです。
type
- この変数に使用するCタイプ。
type
は、タイプを指定するPython文字列である必要があります。int
。 これがポインタ型の場合、型文字列は' *'
で終わる必要があります。 default
- Python値としての、このパラメーターのPythonデフォルト値。 または、デフォルトがない場合は、魔法の値
unspecified
。 py_default
default
は、Pythonコードに文字列として表示されるはずです。 または、デフォルトがない場合はNone
。c_default
default
は、Cコードに文字列として表示されるはずです。 または、デフォルトがない場合はNone
。c_ignored_default
- デフォルトがない場合にC変数を初期化するために使用されるデフォルト値ですが、デフォルトを指定しないと、「初期化されていない変数」の警告が表示される場合があります。 これは、オプショングループを使用する場合に簡単に発生する可能性があります。適切に記述されたコードが実際にこの値を使用することはありませんが、変数はimplに渡され、Cコンパイラは初期化されていない値の「使用」について文句を言います。 この値は常に空でない文字列である必要があります。
converter
- 文字列としてのCコンバーター関数の名前。
impl_by_reference
- ブール値。 trueの場合、引数クリニックは、変数をimpl関数に渡すときに、変数の名前の前に
&
を追加します。 parse_by_reference
- ブール値。 trueの場合、引数クリニックは、変数を PyArg_ParseTuple()に渡すときに、変数の名前の前に
&
を追加します。
Modules/zlibmodule.c
のカスタムコンバーターの最も簡単な例を次に示します。
/*[python input]
class ssize_t_converter(CConverter):
type = 'Py_ssize_t'
converter = 'ssize_t_converter'
[python start generated code]*/
/*[python end generated code: output=da39a3ee5e6b4b0d input=35521e4e733823c7]*/
このブロックは、ssize_t
という名前のコンバーターを引数クリニックに追加します。 ssize_t
として宣言されたパラメーターは、タイプPy_ssize_t
として宣言され、ssize_t_converter
コンバーター関数を呼び出す'O&'
フォーマットユニットによって解析されます。 ssize_t
変数は自動的にデフォルト値をサポートします。
より洗練されたカスタムコンバーターは、初期化とクリーンアップを処理するためにカスタムCコードを挿入できます。 カスタムコンバーターのその他の例は、CPythonソースツリーで確認できます。 文字列CConverter
のCファイルをgrepします。
カスタムリターンコンバーターの作成
カスタムリターンコンバーターを作成することは、カスタムコンバーターを作成することによく似ています。 リターンコンバーター自体がはるかに単純であるため、それがいくらか単純であることを除いて。
リターンコンバーターはCReturnConverter
をサブクラス化する必要があります。 カスタムリターンコンバーターはまだ広く使用されていないため、まだ例はありません。 独自のリターンコンバーターを作成する場合は、Tools/clinic/clinic.py
、特にCReturnConverter
とそのすべてのサブクラスの実装をお読みください。
METH_OおよびMETH_NOARGS
METH_O
を使用して関数を変換するには、関数の単一の引数がobject
コンバーターを使用していることを確認し、引数を位置のみとしてマークします。
/*[clinic input]
meth_o_sample
argument: object
/
[clinic start generated code]*/
METH_NOARGS
を使用して関数を変換するには、引数を指定しないでください。
セルフコンバーター、リターンコンバーターを引き続き使用し、METH_O
のオブジェクトコンバーターにtype
引数を指定できます。
tp_newおよびtp_init関数
tp_new
およびtp_init
関数を変換できます。 必要に応じて、__new__
または__init__
という名前を付けてください。 ノート:
__new__
に対して生成された関数名は、デフォルトのように__new__
で終わることはありません。 これはクラスの名前であり、有効なC識別子に変換されます。- これらの関数に対して
PyMethodDef
#define
は生成されません。 __init__
関数は、PyObject *
ではなくint
を返します。- docstringをクラスdocstringとして使用します。
__new__
関数と__init__
関数は、常にargs
オブジェクトとkwargs
オブジェクトの両方を受け入れる必要がありますが、変換するときに、これらの関数に任意の署名を指定できます。 (関数がキーワードをサポートしていない場合、生成された解析関数は例外を受信すると例外をスローします。)
クリニックの出力の変更とリダイレクト
クリニックの出力を従来の手動で編集されたCコードに散在させるのは不便な場合があります。 幸いなことに、クリニックは構成可能です。後で(または早く!)印刷するために出力をバッファリングするか、出力を別のファイルに書き込むことができます。 クリニックで生成された出力のすべての行に接頭辞または接尾辞を追加することもできます。
この方法でクリニックの出力を変更すると、読みやすさが向上する可能性がありますが、定義される前に型を使用するクリニックコード、または定義される前にクリニックで生成されたコードを使用しようとするコードが発生する可能性があります。 これらの問題は、ファイル内の宣言を再配置するか、クリニックで生成されたコードの移動先に移動することで簡単に解決できます。 (これが、クリニックのデフォルトの動作がすべてを現在のブロックに出力することである理由です。多くの人がこれが読みやすさを妨げると考えていますが、使用前の定義の問題を修正するためにコードを再配置する必要はありません。)
いくつかの用語を定義することから始めましょう:
- 分野
このコンテキストでは、フィールドはクリニックの出力のサブセクションです。 たとえば、
PyMethodDef
構造の#define
は、methoddef_define
と呼ばれるフィールドです。 クリニックには、関数定義ごとに出力できる7つの異なるフィールドがあります。docstring_prototype docstring_definition methoddef_define impl_prototype parser_prototype parser_definition impl_definition
すべての名前は
"<a>_<b>"
の形式です。ここで、"<a>"
は、表されるセマンティックオブジェクト(解析関数、impl関数、docstring、またはmethoddef構造)および"<b>"
です。フィールドがどのようなステートメントであるかを表します。"_prototype"
で終わるフィールド名は、Thingの実際の本体/データなしで、そのThingの前方宣言を表します。"_definition"
で終わるフィールド名は、Thingの実際の定義と、Thingの本体/データを表します。 ("methoddef"
は特別で、"_define"
で終わる唯一のものであり、プリプロセッサ#defineであることを表します。)- 行き先
宛先は、クリニックが出力を書き込むことができる場所です。 5つの組み込みの宛先があります。
block
デフォルトの宛先:現在のClinicブロックの出力セクションに出力されます。
buffer
後で使用するためにテキストを保存できるテキストバッファ。 ここに送信されたテキストは、既存のテキストの最後に追加されます。 クリニックがファイルの処理を終了したときに、バッファにテキストが残っているとエラーになります。
file
クリニックによって自動的に作成される別の「クリニックファイル」。 ファイル用に選択されたファイル名は
{basename}.clinic{extension}
です。ここで、basename
とextension
には、現在のファイルで実行されるos.path.splitext()
からの出力が割り当てられています。 (例:_pickle.c
のfile
宛先は_pickle.clinic.c
に書き込まれます。)重要:を使用する場合
file
目的地、あなた チェックインする必要があります 生成されたファイル!two-pass
buffer
のようなバッファ。 ただし、2パスバッファは1回しかダンプできず、ダンプポイントの後のクリニックブロックからでも、すべての処理中に送信されたすべてのテキストを出力します。suppress
テキストは抑制され、破棄されます。
クリニックは、出力を再構成できる5つの新しいディレクティブを定義しています。
最初の新しいディレクティブはdump
です。
dump <destination>
これにより、指定された宛先の現在の内容が現在のブロックの出力にダンプされ、空になります。 これは、buffer
およびtwo-pass
の宛先でのみ機能します。
2番目の新しいディレクティブはoutput
です。 output
の最も基本的な形式は次のとおりです。
output <field> <destination>
これはクリニックにフィールドを宛先に出力するように指示します。 output
は、everything
と呼ばれる特別なメタ宛先もサポートします。これは、すべてのフィールドをその宛先に出力するようにクリニックに指示します。
output
には、他にもいくつかの機能があります。
output push
output pop
output preset <preset>
output push
およびoutput pop
を使用すると、内部構成スタックで構成をプッシュおよびポップできるため、出力構成を一時的に変更してから、以前の構成を簡単に復元できます。 変更前にプッシュして現在の構成を保存し、前の構成を復元するときにポップします。
output preset
は、次のように、クリニックの出力をいくつかの組み込みプリセット構成の1つに設定します。
block
クリニックの元の開始構成。 入力ブロックの直後にすべてを書き込みます。
parser_prototype
とdocstring_prototype
を抑制し、それ以外はすべてblock
に書き込みます。file
可能な限りすべてを「クリニックファイル」に書き込むように設計されています。 次に、このファイルをファイルの先頭近くで
#include
します。 これを機能させるには、ファイルを再配置する必要がある場合がありますが、通常、これは、さまざまなtypedef
およびPyTypeObject
定義の前方宣言を作成することを意味します。
parser_prototype
とdocstring_prototype
を抑制し、impl_definition
をblock
に書き込み、それ以外はすべてfile
に書き込みます。デフォルトのファイル名は
"{dirname}/clinic/{basename}.h"
です。buffer
クリニックからの出力のほとんどを保存して、最後の方でファイルに書き込みます。 モジュールまたは組み込み型を実装するPythonファイルの場合、モジュールまたは組み込み型の静的構造のすぐ上にバッファーをダンプすることをお勧めします。 これらは通常、終わりに非常に近いです。 ファイルの途中に静的な
PyMethodDef
配列が定義されている場合、buffer
を使用すると、file
よりもさらに多くの編集が必要になる場合があります。
parser_prototype
、impl_prototype
、docstring_prototype
を抑制し、impl_definition
をblock
に書き込み、それ以外はすべてfile
に書き込みます。 ]。two-pass
buffer
プリセットに似ていますが、前方宣言をtwo-pass
バッファーに書き込み、定義をbuffer
に書き込みます。 これはbuffer
プリセットに似ていますが、buffer
よりも編集が少なくて済む場合があります。buffer
プリセットを使用する場合と同じように、ファイルの先頭近くにtwo-pass
バッファーをダンプし、最後近くにbuffer
をダンプします。
impl_prototype
を抑制し、impl_definition
をblock
に書き込み、docstring_prototype
、methoddef_define
、およびparser_prototype
をtwo-pass
、それ以外はすべてbuffer
に書き込みます。partial-buffer
buffer
プリセットに似ていますが、block
にさらに多くのものを書き込み、生成されたコードの非常に大きなチャンクのみをbuffer
に書き込みます。 これにより、buffer
の使用前の定義の問題が完全に回避されますが、ブロックの出力にわずかに多くのものが含まれるという小さなコストがかかります。buffer
プリセットを使用する場合と同じように、buffer
を最後の方にダンプします。
impl_prototype
を抑制し、docstring_definition
とparser_definition
をbuffer
に書き込み、それ以外はすべてblock
に書き込みます。
3番目の新しいディレクティブはdestination
です。
destination <name> <command> [...]
これにより、name
という名前の宛先で操作が実行されます。
new
とclear
の2つの定義済みサブコマンドがあります。
new
サブコマンドは次のように機能します。
destination <name> new <type>
これにより、名前<name>
、タイプ<type>
の新しい宛先が作成されます。
5つの宛先タイプがあります。
suppress
テキストを破棄します。
block
現在のブロックにテキストを書き込みます。 これはクリニックが最初にしたことです。
buffer
上記の「バッファ」組み込み宛先のような単純なテキストバッファ。
file
テキストファイル。 ファイルの宛先は、次のように、ファイル名の作成に使用するテンプレートである追加の引数を取ります。
行き先新着
テンプレートは、ファイル名のビットに置き換えられる3つの文字列を内部で使用できます。
- {path}
ディレクトリと完全なファイル名を含む、ファイルへのフルパス。
- {dirname}
ファイルが存在するディレクトリの名前。
- {basename}
ディレクトリを含まない、ファイルの名前のみ。
- {basename_root}
拡張子が切り取られたベース名(最後の「。」までのすべて。ただし、最後の「。」は含まれません)。
- {basename_extension}
最後 '。' そしてその後のすべて。 ベース名にピリオドが含まれていない場合、これは空の文字列になります。
ファイル名にピリオドがない場合、{basename}と{filename}は同じであり、{extension}は空です。 「{basename} {extension}」は常に「{filename}」とまったく同じです。」
two-pass
上記の「2パス」組み込み宛先のような2パスバッファ。
clear
サブコマンドは次のように機能します。
destination <name> clear
宛先のこの時点までに蓄積されたすべてのテキストを削除します。 (これが何のために必要かはわかりませんが、誰かが実験しているときに役立つかもしれないと思いました。)
4番目の新しいディレクティブはset
です。
set line_prefix "string"
set line_suffix "string"
set
を使用すると、クリニックで2つの内部変数を設定できます。 line_prefix
は、クリニックの出力のすべての行の先頭に追加される文字列です。 line_suffix
は、クリニックの出力のすべての行に追加される文字列です。
これらは両方とも2つのフォーマット文字列をサポートします。
{block comment start}
- 文字列
/*
に変わります。これは、Cファイルの開始コメントテキストシーケンスです。{block comment end}
- 文字列
*/
に変わります。これは、Cファイルのエンドコメントテキストシーケンスです。
最後の新しいディレクティブは、preserve
と呼ばれる、直接使用する必要のないディレクティブです。
preserve
これは、出力の現在の内容を変更せずに保持する必要があることをクリニックに通知します。 これは、出力をfile
ファイルにダンプするときにクリニックによって内部的に使用されます。 Clinicブロックでラップすると、Clinicは既存のチェックサム機能を使用して、ファイルが上書きされる前に手動で変更されていないことを確認できます。
#ifdefトリック
すべてのプラットフォームで使用できるわけではない関数を変換する場合は、作業を少し楽にするために使用できるトリックがあります。 既存のコードはおそらく次のようになります。
#ifdef HAVE_FUNCTIONNAME
static module_functionname(...)
{
...
}
#endif /* HAVE_FUNCTIONNAME */
そして、下部のPyMethodDef
構造では、既存のコードは次のようになります。
#ifdef HAVE_FUNCTIONNAME
{'functionname', ... },
#endif /* HAVE_FUNCTIONNAME */
このシナリオでは、次のように、impl関数の本体を#ifdef
内に囲む必要があります。
#ifdef HAVE_FUNCTIONNAME
/*[clinic input]
module.functionname
...
[clinic start generated code]*/
static module_functionname(...)
{
...
}
#endif /* HAVE_FUNCTIONNAME */
次に、これらの3行をPyMethodDef
構造から削除し、生成されたマクロ引数クリニックに置き換えます。
MODULE_FUNCTIONNAME_METHODDEF
(このマクロの実際の名前は、生成されたコード内にあります。 または、自分で計算することもできます。これは、ブロックの最初の行で定義されている関数の名前ですが、ピリオドがアンダースコアに変更され、大文字になり、"_METHODDEF"
が最後に追加されます。)
おそらくあなたは疑問に思っているでしょう:HAVE_FUNCTIONNAME
が定義されていない場合はどうなりますか? MODULE_FUNCTIONNAME_METHODDEF
マクロも定義されません!
ここで、引数クリニックが非常に賢くなります。 実際には、#ifdef
によってArgumentClinicブロックが非アクティブ化されている可能性があることを検出します。 その場合、次のような少し余分なコードが生成されます。
#ifndef MODULE_FUNCTIONNAME_METHODDEF
#define MODULE_FUNCTIONNAME_METHODDEF
#endif /* !defined(MODULE_FUNCTIONNAME_METHODDEF) */
つまり、マクロは常に機能します。 関数が定義されている場合、これは末尾のコンマを含む正しい構造になります。 関数が未定義の場合、これは何にもなりません。
ただし、これは1つの厄介な問題を引き起こします。「ブロック」出力プリセットを使用する場合、引数クリニックはこの余分なコードをどこに配置する必要がありますか? #ifdef
によって非アクティブ化される可能性があるため、出力ブロックに入れることはできません。 (それが要点です!)
この状況では、引数クリニックは追加のコードを「バッファ」の宛先に書き込みます。 これは、引数クリニックから苦情を受けることを意味する場合があります。
Warning in file "Modules/posixmodule.c" on line 12357:
Destination buffer 'buffer' not empty at end of file, emptying.
これが発生した場合は、ファイルを開き、引数クリニックがファイルに追加したdump buffer
ブロックを見つけて(一番下にあります)、PyMethodDef
構造の上に移動します。マクロが使用されます。
Pythonファイルでの引数クリニックの使用
引数クリニックを使用してPythonファイルを前処理することは実際に可能です。 もちろん、出力がPythonインタープリターにとって意味をなさないため、ArgumentClinicブロックを使用する意味はありません。 しかし、引数クリニックを使用してPythonブロックを実行すると、PythonをPythonプリプロセッサとして使用できます。
PythonコメントはCコメントとは異なるため、Pythonファイルに埋め込まれた引数クリニックブロックは少し異なって見えます。 彼らはこのように見えます:
#/*[python input]
#print("def foo(): pass")
#[python start generated code]*/
def foo(): pass
#/*[python checksum:...]*/