拡張/埋め込みに関するFAQ—Pythonドキュメント

提供:Dev Guides
< PythonPython/docs/3.9/faq/extending
移動先:案内検索

拡張/埋め込みに関するFAQ

コンテンツ


Cで独自の関数を作成できますか?

はい、Cで関数、変数、例外、さらには新しい型を含む組み込みモジュールを作成できます。 これは、ドキュメント Pythonインタープリターの拡張と埋め込みで説明されています。

ほとんどの中級または上級のPythonの本も、このトピックをカバーしています。


C ++で独自の関数を作成できますか?

はい、C ++にあるC互換機能を使用しています。 Pythonインクルードファイルの周囲にextern "C" { ... }を配置し、Pythonインタープリターによって呼び出される各関数の前にextern "C"を配置します。 コンストラクターを使用したグローバルまたは静的C ++オブジェクトは、おそらくお勧めできません。


Cを書くのは難しいです。 代替手段はありますか?

何をしようとしているかに応じて、独自のC拡張機能を作成する代わりにいくつかの方法があります。

Cython とその相対的な Pyrex は、わずかに変更された形式のPythonを受け入れ、対応するCコードを生成するコンパイラーです。 CythonとPyrexを使用すると、PythonのCAPIを学習しなくても拡張機能を作成できます。

現在Python拡張機能が存在しないCまたはC ++ライブラリにインターフェイスする必要がある場合は、 SWIG などのツールを使用してライブラリのデータ型と関数をラップしてみてください。 SIPCXX Boost 、または Weave も、C ++ライブラリをラップするための代替手段です。


Cから任意のPythonステートメントを実行するにはどうすればよいですか?

これを行うための最高レベルの関数は PyRun_SimpleString()で、モジュール__main__のコンテキストで実行される単一の文字列引数を取り、成功した場合は0を返します。例外が発生したときの-1SyntaxError を含む)。 より詳細な制御が必要な場合は、 PyRun_String()を使用してください。 Python/pythonrun.cPyRun_SimpleString()のソースを参照してください。


Cから任意のPython式を評価するにはどうすればよいですか?

前の質問の関数 PyRun_String()を、開始記号 Py_eval_input で呼び出します。 式を解析して評価し、その値を返します。


PythonオブジェクトからC値を抽出するにはどうすればよいですか?

それはオブジェクトのタイプに依存します。 タプルの場合、 PyTuple_Size()はその長さを返し、 PyTuple_GetItem()は指定されたインデックスのアイテムを返します。 リストには、PyListSize()PyList_GetItem()という同様の関数があります。

バイトの場合、 PyBytes_Size()はその長さを返し、 PyBytes_AsStringAndSize()はその値とその長さへのポインターを提供します。 Pythonバイトオブジェクトにはnullバイトが含まれている可能性があるため、Cのstrlen()は使用しないでください。

オブジェクトのタイプをテストするには、まずオブジェクトがNULLでないことを確認してから、 PyBytes_Check()PyTuple_Check()PyList_Check( )など。

いわゆる「抽象」インターフェースによって提供されるPythonオブジェクトへの高レベルAPIもあります。詳細については、Include/abstract.hを参照してください。 PySequence_Length()PySequence_GetItem()などの呼び出しを使用して、あらゆる種類のPythonシーケンスとのインターフェースを可能にします。 また、数値( PyNumber_Index()など)やPyMappingAPIのマッピングなどの他の多くの便利なプロトコルもあります。


Py_BuildValue()を使用して任意の長さのタプルを作成するにはどうすればよいですか?

できません。 代わりに PyTuple_Pack()を使用してください。


Cからオブジェクトのメソッドを呼び出すにはどうすればよいですか?

PyObject_CallMethod()関数を使用して、オブジェクトの任意のメソッドを呼び出すことができます。 パラメータは、オブジェクト、呼び出すメソッドの名前、 Py_BuildValue()で使用されるようなフォーマット文字列、および引数値です。

PyObject *
PyObject_CallMethod(PyObject *object, const char *method_name,
                    const char *arg_format, ...);

これは、組み込みかユーザー定義かに関係なく、メソッドを持つすべてのオブジェクトで機能します。 最終的には、戻り値を Py_DECREF()する必要があります。

たとえば、引数10、0を使用してファイルオブジェクトの「seek」メソッドを呼び出すには(ファイルオブジェクトポインタが「f」であると想定):

res = PyObject_CallMethod(f, "seek", "(ii)", 10, 0);
if (res == NULL) {
        ... an exception occurred ...
}
else {
        Py_DECREF(res);
}

PyObject_CallObject() always は引数リストのタプルを必要としているため、引数なしで関数を呼び出すには、形式に「()」を渡し、1つを使用して関数を呼び出すことに注意してください。引数、括弧で引数を囲みます。例: "(私)"。


PyErr_Print()(またはstdout / stderrに出力するもの)からの出力をキャッチするにはどうすればよいですか?

Pythonコードで、write()メソッドをサポートするオブジェクトを定義します。 このオブジェクトを sys.stdout および sys.stderr に割り当てます。 print_errorを呼び出すか、標準のトレースバックメカニズムが機能するようにします。 次に、write()メソッドが送信する場所に出力が送信されます。

これを行う最も簡単な方法は、 io.StringIO クラスを使用することです。

>>> import io, sys
>>> sys.stdout = io.StringIO()
>>> print('foo')
>>> print('hello world!')
>>> sys.stderr.write(sys.stdout.getvalue())
foo
hello world!

同じことを行うカスタムオブジェクトは次のようになります。

>>> import io, sys
>>> class StdoutCatcher(io.TextIOBase):
...     def __init__(self):
...         self.data = []
...     def write(self, stuff):
...         self.data.append(stuff)
...
>>> import sys
>>> sys.stdout = StdoutCatcher()
>>> print('foo')
>>> print('hello world!')
>>> sys.stderr.write(''.join(sys.stdout.data))
foo
hello world!

CからPythonで記述されたモジュールにアクセスするにはどうすればよいですか?

次のように、モジュールオブジェクトへのポインタを取得できます。

module = PyImport_ImportModule("<modulename>");

モジュールがまだインポートされていない場合(つまり、 sys.modules )にはまだ存在していません。これにより、モジュールが初期化されます。 それ以外の場合は、単にsys.modules["<modulename>"]の値を返します。 モジュールを名前空間に入力しないことに注意してください。モジュールが初期化され、 sys.modules に格納されていることを確認するだけです。

その後、モジュールの属性にアクセスできます(つまり、 モジュールで定義されている名前)は次のとおりです。

attr = PyObject_GetAttrString(module, "<attrname>");

PyObject_SetAttrString()を呼び出して、モジュール内の変数に割り当てることもできます。


PythonからC ++オブジェクトにインターフェイスするにはどうすればよいですか?

要件に応じて、多くのアプローチがあります。 これを手動で行うには、「拡張と埋め込み」ドキュメントを読むことから始めます。 Pythonランタイムシステムの場合、CとC ++の間に大きな違いはないことを理解してください。したがって、C構造体(ポインター)型を中心に新しいPython型を構築する戦略は、C ++オブジェクトでも機能します。

C ++ライブラリについては、 Cの記述が難しいを参照してください。 代替案はありますか?


セットアップファイルを使用してモジュールを追加しましたが、makeが失敗します。 どうして?

セットアップは改行で終了する必要があります。改行がない場合、ビルドプロセスは失敗します。 (これを修正するには、いくつかの醜いシェルスクリプトハッカーが必要です。このバグは非常に小さいため、努力する価値はないようです。)


拡張機能をデバッグするにはどうすればよいですか?

動的にロードされた拡張機能でGDBを使用する場合、拡張機能がロードされるまで、拡張機能にブレークポイントを設定することはできません。

.gdbinitファイルに(またはインタラクティブに)次のコマンドを追加します。

br _PyImport_LoadDynamicModule

次に、GDBを実行すると:

$ gdb /local/bin/python
gdb) run myscript.py
gdb) continue # repeat until your extension is loaded
gdb) finish   # so that your extension is loaded
gdb) br myfunction.c:50
gdb) continue

LinuxシステムでPythonモジュールをコンパイルしたいのですが、いくつかのファイルがありません。 どうして?

Pythonのほとんどのパッケージバージョンには、Python拡張機能のコンパイルに必要なさまざまなファイルを含む/usr/lib/python2.x/config/ディレクトリが含まれていません。

Red Hatの場合、必要なファイルを取得するためにpython-develRPMをインストールします。

Debianの場合は、apt-get install python-devを実行します。


「不完全な入力」と「無効な入力」を区別するにはどうすればよいですか?

Pythonインタラクティブインタプリタの動作をエミュレートしたい場合があります。この動作では、入力が不完全な場合に継続プロンプトが表示されます(例: 「if」ステートメントの先頭を入力したか、括弧または3重引用符を閉じなかった場合)、入力が無効な場合はすぐに構文エラーメッセージが表示されます。

Pythonでは、 codeop モジュールを使用できます。これは、パーサーの動作を十分に近似します。 たとえば、IDLEはこれを使用します。

Cでそれを行う最も簡単な方法は、 PyRun_InteractiveLoop()(おそらく別のスレッドで)を呼び出し、Pythonインタープリターに入力を処理させることです。 PyOS_ReadlineFunctionPointer()を設定して、カスタム入力関数を指すようにすることもできます。 その他のヒントについては、Modules/readline.cおよびParser/myreadline.cを参照してください。

ただし、組み込みPythonインタープリターをRESTアプリケーションと同じスレッドで実行する必要があり、ユーザー入力の待機中に PyRun_InteractiveLoop()を停止できない場合があります。 1つの解決策は、PyParser_ParseString()を呼び出し、e.errorE_EOFに等しいかどうかをテストすることです。これは、入力が不完全であることを意味します。 Alex Farberのコードに触発された、テストされていないサンプルコードフラグメントを次に示します。

#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <node.h>
#include <errcode.h>
#include <grammar.h>
#include <parsetok.h>
#include <compile.h>

int testcomplete(char *code)
  /* code should end in \n */
  /* return -1 for error, 0 for incomplete, 1 for complete */
{
  node *n;
  perrdetail e;

  n = PyParser_ParseString(code, &_PyParser_Grammar,
                           Py_file_input, &e);
  if (n == NULL) {
    if (e.error == E_EOF)
      return 0;
    return -1;
  }

  PyNode_Free(n);
  return 1;
}

別の解決策は、受信した文字列を Py_CompileString()でコンパイルしようとすることです。 エラーなしでコンパイルされる場合は、 PyEval_EvalCode()を呼び出して、返されたコードオブジェクトを実行してみてください。 それ以外の場合は、後で使用するために入力を保存します。 コンパイルが失敗した場合は、それがエラーなのか、それともさらに入力が必要なのかを調べます。例外タプルからメッセージ文字列を抽出し、「解析中に予期しないEOF」という文字列と比較します。 以下は、GNU readlineライブラリを使用した完全な例です(readline()の呼び出し中に SIGINT を無視することをお勧めします)。

#include <stdio.h>
#include <readline.h>

#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <object.h>
#include <compile.h>
#include <eval.h>

int main (int argc, char* argv[])
{
  int i, j, done = 0;                          /* lengths of line, code */
  char ps1[] = ">>> ";
  char ps2[] = "... ";
  char *prompt = ps1;
  char *msg, *line, *code = NULL;
  PyObject *src, *glb, *loc;
  PyObject *exc, *val, *trb, *obj, *dum;

  Py_Initialize ();
  loc = PyDict_New ();
  glb = PyDict_New ();
  PyDict_SetItemString (glb, "__builtins__", PyEval_GetBuiltins ());

  while (!done)
  {
    line = readline (prompt);

    if (NULL == line)                          /* Ctrl-D pressed */
    {
      done = 1;
    }
    else
    {
      i = strlen (line);

      if (i > 0)
        add_history (line);                    /* save non-empty lines */

      if (NULL == code)                        /* nothing in code yet */
        j = 0;
      else
        j = strlen (code);

      code = realloc (code, i + j + 2);
      if (NULL == code)                        /* out of memory */
        exit (1);

      if (0 == j)                              /* code was empty, so */
        code[0] = '\0';                        /* keep strncat happy */

      strncat (code, line, i);                 /* append line to code */
      code[i + j] = '\n';                      /* append '\n' to code */
      code[i + j + 1] = '\0';

      src = Py_CompileString (code, "<stdin>", Py_single_input);

      if (NULL != src)                         /* compiled just fine - */
      {
        if (ps1  == prompt ||                  /* ">>> " or */
            '\n' == code[i + j - 1])           /* "... " and double '\n' */
        {                                               /* so execute it */
          dum = PyEval_EvalCode (src, glb, loc);
          Py_XDECREF (dum);
          Py_XDECREF (src);
          free (code);
          code = NULL;
          if (PyErr_Occurred ())
            PyErr_Print ();
          prompt = ps1;
        }
      }                                        /* syntax error or E_EOF? */
      else if (PyErr_ExceptionMatches (PyExc_SyntaxError))
      {
        PyErr_Fetch (&exc, &val, &trb);        /* clears exception! */

        if (PyArg_ParseTuple (val, "sO", &msg, &obj) &&
            !strcmp (msg, "unexpected EOF while parsing")) /* E_EOF */
        {
          Py_XDECREF (exc);
          Py_XDECREF (val);
          Py_XDECREF (trb);
          prompt = ps2;
        }
        else                                   /* some other syntax error */
        {
          PyErr_Restore (exc, val, trb);
          PyErr_Print ();
          free (code);
          code = NULL;
          prompt = ps1;
        }
      }
      else                                     /* some non-syntax error */
      {
        PyErr_Print ();
        free (code);
        code = NULL;
        prompt = ps1;
      }

      free (line);
    }
  }

  Py_XDECREF(glb);
  Py_XDECREF(loc);
  Py_Finalize();
  exit(0);
}

未定義のg ++シンボル__builtin_newまたは__pure_virtualを見つけるにはどうすればよいですか?

g ++拡張モジュールを動的にロードするには、Pythonを再コンパイルし、g ++を使用して再リンクし(Python Modules MakefileでLINKCCを変更)、g ++を使用して拡張モジュールをリンクする必要があります(例:g++ -shared -o mymodule.so mymodule.o)。


Cで実装されたメソッドとPythonで実装されたメソッドを使用してオブジェクトクラスを作成できますか(例: 継承を通じて)?

はい、 intlistdict などの組み込みクラスから継承できます。

Boost Pythonライブラリ(BPL、 http://www.boost.org/libs/python/doc/index.html )は、C ++からこれを行う方法を提供します(つまり、 BPLを使用してC ++で記述された拡張クラスから継承できます)。