2. 拡張タイプの定義:チュートリアル—Pythonドキュメント

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

2.2。 拡張タイプの定義:チュートリアル

Pythonを使用すると、C拡張モジュールの作成者は、組み込みの str および list 型と同様に、Pythonコードから操作できる新しい型を定義できます。 すべての拡張タイプのコードはパターンに従いますが、開始する前に理解する必要のある詳細がいくつかあります。 このドキュメントは、このトピックの穏やかな紹介です。

2.1。 基礎

CPython ランタイムは、すべてのPythonオブジェクトを PyObject * 型の変数として認識します。これは、すべてのPythonオブジェクトの「基本型」として機能します。 PyObject 構造自体には、オブジェクトの参照カウントとオブジェクトの「タイプオブジェクト」へのポインタのみが含まれます。 これがアクションの場所です。 タイプオブジェクトは、たとえば、属性がオブジェクトで検索されたとき、メソッドが呼び出されたとき、または別のオブジェクトが乗算されたときに、インタプリタによって呼び出される(C)関数を決定します。 これらのC関数は「タイプメソッド」と呼ばれます。

したがって、新しい拡張タイプを定義する場合は、新しいタイプオブジェクトを作成する必要があります。

この種のことは例によってのみ説明できるので、C拡張モジュールcustom内にCustomという名前の新しいタイプを定義する最小限の完全なモジュールを次に示します。

ノート

ここで示しているのは、静的拡張タイプを定義する従来の方法です。 ほとんどの用途に適しているはずです。 C APIでは、 PyType_FromSpec()関数を使用してヒープに割り当てられた拡張タイプを定義することもできますが、このチュートリアルでは説明していません。


#define PY_SSIZE_T_CLEAN
#include <Python.h>

typedef struct {
    PyObject_HEAD
    /* Type-specific fields go here. */
} CustomObject;

static PyTypeObject CustomType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "custom.Custom",
    .tp_doc = "Custom objects",
    .tp_basicsize = sizeof(CustomObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT,
    .tp_new = PyType_GenericNew,
};

static PyModuleDef custommodule = {
    PyModuleDef_HEAD_INIT,
    .m_name = "custom",
    .m_doc = "Example module that creates an extension type.",
    .m_size = -1,
};

PyMODINIT_FUNC
PyInit_custom(void)
{
    PyObject *m;
    if (PyType_Ready(&CustomType) < 0)
        return NULL;

    m = PyModule_Create(&custommodule);
    if (m == NULL)
        return NULL;

    Py_INCREF(&CustomType);
    if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {
        Py_DECREF(&CustomType);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}

これは一度に理解するのにかなりの量ですが、前の章から少しおなじみのように思われることを願っています。 このファイルは3つのことを定義します:

  1. Custom オブジェクトに含まれるもの:これはCustomObject構造体であり、Customインスタンスごとに1回割り当てられます。
  2. Custom type の動作:これはCustomType構造体であり、特定の操作が要求されたときにインタープリターが検査するフラグと関数ポインターのセットを定義します。
  3. customモジュールを初期化する方法:これはPyInit_custom関数と関連するcustommodule構造体です。

最初のビットは次のとおりです。

typedef struct {
    PyObject_HEAD
} CustomObject;

これは、カスタムオブジェクトに含まれるものです。 PyObject_HEADは、各オブジェクト構造体の開始時に必須であり、タイプオブジェクトへのポインターと参照カウントを含むタイプ PyObjectob_baseと呼ばれるフィールドを定義します(これらはマクロ Py_REFCNT および Py_TYPE をそれぞれ使用してアクセスします。 マクロの理由は、レイアウトを抽象化し、デバッグビルドで追加のフィールドを有効にするためです。

ノート

PyObject_HEAD マクロの後にセミコロンはありません。 誤って追加することに注意してください。一部のコンパイラは文句を言います。


もちろん、オブジェクトは通常、標準のPyObject_HEADボイラープレートに加えて追加のデータを格納します。 たとえば、標準のPythonフロートの定義は次のとおりです。

typedef struct {
    PyObject_HEAD
    double ob_fval;
} PyFloatObject;

2番目のビットは、タイプオブジェクトの定義です。

static PyTypeObject CustomType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "custom.Custom",
    .tp_doc = "Custom objects",
    .tp_basicsize = sizeof(CustomObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT,
    .tp_new = PyType_GenericNew,
};

ノート

気にしないすべての PyTypeObject フィールドをリストしないように、またフィールドの宣言順序を気にしないように、上記のようにC99スタイルの指定イニシャライザーを使用することをお勧めします。


object.hPyTypeObject の実際の定義には、上記の定義よりもはるかに多くのフィールドがあります。 残りのフィールドはCコンパイラによってゼロで埋められます。必要がない限り、明示的に指定しないのが一般的な方法です。

一度に1つのフィールドでそれを分解します。

PyVarObject_HEAD_INIT(NULL, 0)

この行は、上記のob_baseフィールドを初期化するための必須の定型文です。

.tp_name = "custom.Custom",

私たちのタイプの名前。 これは、オブジェクトのデフォルトのテキスト表現といくつかのエラーメッセージに表示されます。次に例を示します。

>>> "" + custom.Custom()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "custom.Custom") to str

名前は、モジュール名とモジュール内のタイプの名前の両方を含む点線の名前であることに注意してください。 この場合のモジュールはcustomで、タイプはCustomなので、タイプ名をcustom.Customに設定します。 タイプを pydoc および pickle モジュールと互換性を持たせるには、実際のドット付きインポートパスを使用することが重要です。

.tp_basicsize = sizeof(CustomObject),
.tp_itemsize = 0,

これは、Pythonが新しいCustomインスタンスを作成するときに割り当てるメモリの量を認識できるようにするためです。 tp_itemsize は可変サイズのオブジェクトにのみ使用され、それ以外の場合はゼロにする必要があります。

ノート

型をPythonからサブクラス化可能にし、型の tp_basicsize が基本型と同じである場合、多重継承で問題が発生する可能性があります。 タイプのPythonサブクラスは、最初にタイプを __ bases __ にリストする必要があります。そうしないと、エラーが発生せずにタイプの__new__()メソッドを呼び出すことができません。 タイプの tp_basicsize の値が基本タイプよりも大きいことを確認することで、この問題を回避できます。 ほとんどの場合、これはとにかく当てはまります。これは、基本タイプがオブジェクトになるか、データメンバーを基本タイプに追加して、そのサイズを大きくするためです。


クラスフラグを Py_TPFLAGS_DEFAULT に設定します。

.tp_flags = Py_TPFLAGS_DEFAULT,

すべてのタイプは、フラグにこの定数を含める必要があります。 少なくともPython3.3までに定義されたすべてのメンバーを有効にします。 さらにメンバーが必要な場合は、対応するフラグをORする必要があります。

tp_doc のタイプのドキュメント文字列を提供します。

.tp_doc = "Custom objects",

オブジェクトの作成を有効にするには、 tp_new ハンドラーを提供する必要があります。 これはPythonメソッド__new__()と同等ですが、明示的に指定する必要があります。 この場合、API関数 PyType_GenericNew()によって提供されるデフォルトの実装を使用できます。

.tp_new = PyType_GenericNew,

PyInit_custom()の一部のコードを除いて、ファイル内の他のすべてはなじみのあるものでなければなりません。

if (PyType_Ready(&CustomType) < 0)
    return;

これにより、Customタイプが初期化され、最初にNULLに設定したob_typeを含む、適切なデフォルト値にいくつかのメンバーが入力されます。

Py_INCREF(&CustomType);
if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {
    Py_DECREF(&CustomType);
    Py_DECREF(m);
    return NULL;
}

これにより、タイプがモジュールディクショナリに追加されます。 これにより、Customクラスを呼び出すことにより、Customインスタンスを作成できます。

>>> import custom
>>> mycustom = custom.Custom()

それでおしまい! 残っているのはそれを構築することだけです。 上記のコードをcustom.cというファイルに入れて:

from distutils.core import setup, Extension
setup(name="custom", version="1.0",
      ext_modules=[Extension("custom", ["custom.c"])])

setup.pyというファイル内。 次に入力します

$ python setup.py build

シェルでは、サブディレクトリにファイルcustom.soを生成する必要があります。 そのディレクトリに移動してPythonを起動します— import customを実行して、カスタムオブジェクトを試すことができるはずです。

それほど難しくはありませんでしたね。

もちろん、現在のカスタムタイプはかなり面白くありません。 データがなく、何もしません。 サブクラス化することもできません。

ノート

このドキュメントでは、C拡張機能を構築するための標準の distutils モジュールを紹介していますが、実際のユースケースでは、より新しく、より適切に保守されているsetuptoolsライブラリを使用することをお勧めします。 これを行う方法に関するドキュメントは、このドキュメントの範囲外であり、 Pythonパッケージングユーザーガイドにあります。


2.2。 基本的な例へのデータとメソッドの追加

基本的な例を拡張して、いくつかのデータとメソッドを追加しましょう。 型も基本クラスとして使えるようにしましょう。 次の機能を追加する新しいモジュールcustom2を作成します。

#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "structmember.h"

typedef struct {
    PyObject_HEAD
    PyObject *first; /* first name */
    PyObject *last;  /* last name */
    int number;
} CustomObject;

static void
Custom_dealloc(CustomObject *self)
{
    Py_XDECREF(self->first);
    Py_XDECREF(self->last);
    Py_TYPE(self)->tp_free((PyObject *) self);
}

static PyObject *
Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    CustomObject *self;
    self = (CustomObject *) type->tp_alloc(type, 0);
    if (self != NULL) {
        self->first = PyUnicode_FromString("");
        if (self->first == NULL) {
            Py_DECREF(self);
            return NULL;
        }
        self->last = PyUnicode_FromString("");
        if (self->last == NULL) {
            Py_DECREF(self);
            return NULL;
        }
        self->number = 0;
    }
    return (PyObject *) self;
}

static int
Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"first", "last", "number", NULL};
    PyObject *first = NULL, *last = NULL, *tmp;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,
                                     &first, &last,
                                     &self->number))
        return -1;

    if (first) {
        tmp = self->first;
        Py_INCREF(first);
        self->first = first;
        Py_XDECREF(tmp);
    }
    if (last) {
        tmp = self->last;
        Py_INCREF(last);
        self->last = last;
        Py_XDECREF(tmp);
    }
    return 0;
}

static PyMemberDef Custom_members[] = {
    {"first", T_OBJECT_EX, offsetof(CustomObject, first), 0,
     "first name"},
    {"last", T_OBJECT_EX, offsetof(CustomObject, last), 0,
     "last name"},
    {"number", T_INT, offsetof(CustomObject, number), 0,
     "custom number"},
    {NULL}  /* Sentinel */
};

static PyObject *
Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))
{
    if (self->first == NULL) {
        PyErr_SetString(PyExc_AttributeError, "first");
        return NULL;
    }
    if (self->last == NULL) {
        PyErr_SetString(PyExc_AttributeError, "last");
        return NULL;
    }
    return PyUnicode_FromFormat("%S %S", self->first, self->last);
}

static PyMethodDef Custom_methods[] = {
    {"name", (PyCFunction) Custom_name, METH_NOARGS,
     "Return the name, combining the first and last name"
    },
    {NULL}  /* Sentinel */
};

static PyTypeObject CustomType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "custom2.Custom",
    .tp_doc = "Custom objects",
    .tp_basicsize = sizeof(CustomObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
    .tp_new = Custom_new,
    .tp_init = (initproc) Custom_init,
    .tp_dealloc = (destructor) Custom_dealloc,
    .tp_members = Custom_members,
    .tp_methods = Custom_methods,
};

static PyModuleDef custommodule = {
    PyModuleDef_HEAD_INIT,
    .m_name = "custom2",
    .m_doc = "Example module that creates an extension type.",
    .m_size = -1,
};

PyMODINIT_FUNC
PyInit_custom2(void)
{
    PyObject *m;
    if (PyType_Ready(&CustomType) < 0)
        return NULL;

    m = PyModule_Create(&custommodule);
    if (m == NULL)
        return NULL;

    Py_INCREF(&CustomType);
    if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {
        Py_DECREF(&CustomType);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}

このバージョンのモジュールには、いくつかの変更があります。

追加のインクルードを追加しました:

#include <structmember.h>

これには、後で説明するように、属性を処理するために使用する宣言が含まれます。

CustomタイプのC構造体には、 firstlast 、および number の3つのデータ属性があります。 first 変数と last 変数は、名前と名前を含むPython文字列です。 number 属性はC整数です。

オブジェクト構造はそれに応じて更新されます。

typedef struct {
    PyObject_HEAD
    PyObject *first; /* first name */
    PyObject *last;  /* last name */
    int number;
} CustomObject;

管理するデータができたので、オブジェクトの割り当てと割り当て解除にもっと注意する必要があります。 少なくとも、割り当て解除方法が必要です。

static void
Custom_dealloc(CustomObject *self)
{
    Py_XDECREF(self->first);
    Py_XDECREF(self->last);
    Py_TYPE(self)->tp_free((PyObject *) self);
}

これは tp_dealloc メンバーに割り当てられています:

.tp_dealloc = (destructor) Custom_dealloc,

このメソッドは、最初に2つのPython属性の参照カウントをクリアします。 Py_XDECREF()は、引数がNULLの場合を正しく処理します(tp_newが途中で失敗した場合に発生する可能性があります)。 次に、オブジェクトのタイプの tp_free メンバー(Py_TYPE(self)によって計算される)を呼び出して、オブジェクトのメモリを解放します。 オブジェクトはサブクラスのインスタンスである可能性があるため、オブジェクトのタイプはCustomTypeではない可能性があることに注意してください。

ノート

Custom_deallocCustomObject *引数を取るように定義したため、上記のdestructorへの明示的なキャストが必要ですが、tp_dealloc関数ポインターはPyObject *引数。 それ以外の場合、コンパイラは警告を発します。 これは、Cではオブジェクト指向のポリモーフィズムです。


名前と名前が空の文字列に初期化されていることを確認したいので、tp_newの実装を提供します。

static PyObject *
Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    CustomObject *self;
    self = (CustomObject *) type->tp_alloc(type, 0);
    if (self != NULL) {
        self->first = PyUnicode_FromString("");
        if (self->first == NULL) {
            Py_DECREF(self);
            return NULL;
        }
        self->last = PyUnicode_FromString("");
        if (self->last == NULL) {
            Py_DECREF(self);
            return NULL;
        }
        self->number = 0;
    }
    return (PyObject *) self;
}

tp_new メンバーにインストールします。

.tp_new = Custom_new,

tp_newハンドラーは、(初期化ではなく)その型のオブジェクトを作成する役割を果たします。 Pythonでは__new__()メソッドとして公開されています。 tp_newメンバーを定義する必要はありません。実際、多くの拡張タイプは、上記のCustomタイプの最初のバージョンで行われたように、 PyType_GenericNew()を再利用します。 この場合、tp_newハンドラーを使用して、firstおよびlast属性をNULL以外のデフォルト値に初期化します。

tp_newには、インスタンス化される型(サブクラスがインスタンス化される場合、必ずしもCustomTypeである必要はありません)と、型が呼び出されたときに渡される引数が渡され、作成されたインスタンスを返すことが期待されます。 tp_newハンドラーは常に位置引数とキーワード引数を受け入れますが、引数を無視することが多く、引数の処理は初期化子(別名 Cのtp_initまたはPythonの__init__)メソッド。

ノート

tp_newは、tp_initを明示的に呼び出すべきではありません。これは、インタープリターがそれ自体を行うためです。


tp_new実装は、 tp_alloc スロットを呼び出してメモリを割り当てます。

self = (CustomObject *) type->tp_alloc(type, 0);

メモリ割り当てが失敗する可能性があるため、続行する前に tp_alloc の結果をNULLと照合する必要があります。

ノート

tp_alloc スロットを自分で埋めませんでした。 むしろ、 PyType_Ready()は、基本クラス(デフォルトではオブジェクト)から継承することで、それを埋めます。 ほとんどのタイプは、デフォルトの割り当て戦略を使用します。


ノート

協同組合 tp_new (基本タイプの tp_new または__new__()を呼び出すもの)を作成する場合は、しないを決定する必要があります。実行時にメソッド解決順序を使用して呼び出すメソッド。 呼び出すタイプを常に静的に決定し、その tp_new を直接、またはtype->tp_base->tp_newを介して呼び出します。 これを行わないと、他のPython定義クラスからも継承するタイプのPythonサブクラスが正しく機能しない可能性があります。 (具体的には、 TypeError を取得せずに、このようなサブクラスのインスタンスを作成できない場合があります。)


また、インスタンスの初期値を提供する引数を受け入れる初期化関数を定義します。

static int
Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"first", "last", "number", NULL};
    PyObject *first = NULL, *last = NULL, *tmp;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,
                                     &first, &last,
                                     &self->number))
        return -1;

    if (first) {
        tmp = self->first;
        Py_INCREF(first);
        self->first = first;
        Py_XDECREF(tmp);
    }
    if (last) {
        tmp = self->last;
        Py_INCREF(last);
        self->last = last;
        Py_XDECREF(tmp);
    }
    return 0;
}

tp_init スロットを埋めることによって。

.tp_init = (initproc) Custom_init,

tp_init スロットは、Pythonでは__init__()メソッドとして公開されています。 作成後にオブジェクトを初期化するために使用されます。 イニシャライザーは常に位置引数とキーワード引数を受け入れ、成功した場合は0を、エラーの場合は-1を返す必要があります。

tp_newハンドラーとは異なり、tp_initが呼び出される保証はありません(たとえば、 pickle モジュールはデフォルトで__init__()を呼び出さない)。選択されていないインスタンスの場合)。 複数回呼び出すこともできます。 オブジェクトに対して誰でも__init__()メソッドを呼び出すことができます。 このため、新しい属性値を割り当てるときは特に注意する必要があります。 たとえば、firstメンバーを次のように割り当てたいと思うかもしれません。

if (first) {
    Py_XDECREF(self->first);
    Py_INCREF(first);
    self->first = first;
}

しかし、これは危険です。 私たちのタイプはfirstメンバーのタイプを制限しないので、どんな種類のオブジェクトでもかまいません。 firstメンバーにアクセスしようとするコードを実行させるデストラクタが含まれている可能性があります。 または、そのデストラクタがグローバルインタプリタロックを解放し、オブジェクトにアクセスして変更する他のスレッドで任意のコードを実行させることができます。

偏執的であり、この可能性から身を守るために、ほとんどの場合、参照数を減らす前にメンバーを再割り当てします。 いつこれをしなければならないのですか?

  • 参照カウントが1より大きいことが絶対にわかっている場合。
  • オブジェクト 1 の割り当て解除によって、 GIL が解放されず、型のコードへの呼び出しが発生しないことがわかっている場合。
  • 循環ガベージコレクション 2 をサポートしないタイプの tp_dealloc ハンドラーで参照カウントをデクリメントする場合。

インスタンス変数を属性として公開したいと思います。 これを行うにはいくつかの方法があります。 最も簡単な方法は、メンバー定義を定義することです。

static PyMemberDef Custom_members[] = {
    {"first", T_OBJECT_EX, offsetof(CustomObject, first), 0,
     "first name"},
    {"last", T_OBJECT_EX, offsetof(CustomObject, last), 0,
     "last name"},
    {"number", T_INT, offsetof(CustomObject, number), 0,
     "custom number"},
    {NULL}  /* Sentinel */
};

定義を tp_members スロットに配置します。

.tp_members = Custom_members,

各メンバー定義には、メンバー名、タイプ、オフセット、アクセスフラグ、およびドキュメント文字列があります。 詳細については、以下の汎用属性管理セクションを参照してください。

このアプローチの欠点は、Python属性に割り当てることができるオブジェクトのタイプを制限する方法が提供されないことです。 姓と名は文字列である必要がありますが、任意のPythonオブジェクトを割り当てることができます。 さらに、CポインタをNULLに設定して、属性を削除することができます。 メンバーがNULL以外の値に初期化されていることを確認できますが、属性が削除されている場合は、メンバーをNULLに設定できます。

オブジェクト名を名前と名前の連結として出力する単一のメソッドCustom.name()を定義します。

static PyObject *
Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))
{
    if (self->first == NULL) {
        PyErr_SetString(PyExc_AttributeError, "first");
        return NULL;
    }
    if (self->last == NULL) {
        PyErr_SetString(PyExc_AttributeError, "last");
        return NULL;
    }
    return PyUnicode_FromFormat("%S %S", self->first, self->last);
}

このメソッドは、Custom(またはCustomサブクラス)インスタンスを最初の引数として取るC関数として実装されます。 メソッドは常に最初の引数としてインスタンスを取ります。 多くの場合、メソッドは位置引数とキーワード引数も取りますが、この場合は何も取りませんし、位置引数タプルまたはキーワード引数辞書を受け入れる必要もありません。 このメソッドは、Pythonメソッドと同等です。

def name(self):
    return "%s %s" % (self.first, self.last)

firstおよびlastメンバーがNULLである可能性を確認する必要があることに注意してください。 これは、削除できるため、NULLに設定されているためです。 これらの属性の削除を防ぎ、属性値を文字列に制限することをお勧めします。 次のセクションでその方法を説明します。

メソッドを定義したので、メソッド定義の配列を作成する必要があります。

static PyMethodDef Custom_methods[] = {
    {"name", (PyCFunction) Custom_name, METH_NOARGS,
     "Return the name, combining the first and last name"
    },
    {NULL}  /* Sentinel */
};

METH_NOARGS フラグを使用して、メソッドが self 以外の引数を予期していないことを示していることに注意してください)

そしてそれを tp_methods スロットに割り当てます。

.tp_methods = Custom_methods,

最後に、タイプをサブクラス化の基本クラスとして使用できるようにします。 これまでのところ、作成または使用されているオブジェクトのタイプについて何も想定しないようにメソッドを注意深く記述しているため、必要なのは Py_TPFLAGS_BASETYPE をクラスフラグに追加することだけです。意味:

.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,

PyInit_custom()の名前をPyInit_custom2()に変更し、 PyModuleDef 構造体のモジュール名を更新し、 PyTypeObject 構造体の完全なクラス名を更新します。

最後に、setup.pyファイルを更新して、新しいモジュールをビルドします。

from distutils.core import setup, Extension
setup(name="custom", version="1.0",
      ext_modules=[
         Extension("custom", ["custom.c"]),
         Extension("custom2", ["custom2.c"]),
         ])

2.3。 データ属性をより細かく制御できます

このセクションでは、Customの例でfirstおよびlast属性を設定する方法をより細かく制御します。 以前のバージョンのモジュールでは、インスタンス変数firstおよびlastを文字列以外の値に設定したり、削除したりすることができました。 これらの属性に常に文字列が含まれていることを確認する必要があります。

#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "structmember.h"

typedef struct {
    PyObject_HEAD
    PyObject *first; /* first name */
    PyObject *last;  /* last name */
    int number;
} CustomObject;

static void
Custom_dealloc(CustomObject *self)
{
    Py_XDECREF(self->first);
    Py_XDECREF(self->last);
    Py_TYPE(self)->tp_free((PyObject *) self);
}

static PyObject *
Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    CustomObject *self;
    self = (CustomObject *) type->tp_alloc(type, 0);
    if (self != NULL) {
        self->first = PyUnicode_FromString("");
        if (self->first == NULL) {
            Py_DECREF(self);
            return NULL;
        }
        self->last = PyUnicode_FromString("");
        if (self->last == NULL) {
            Py_DECREF(self);
            return NULL;
        }
        self->number = 0;
    }
    return (PyObject *) self;
}

static int
Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"first", "last", "number", NULL};
    PyObject *first = NULL, *last = NULL, *tmp;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,
                                     &first, &last,
                                     &self->number))
        return -1;

    if (first) {
        tmp = self->first;
        Py_INCREF(first);
        self->first = first;
        Py_DECREF(tmp);
    }
    if (last) {
        tmp = self->last;
        Py_INCREF(last);
        self->last = last;
        Py_DECREF(tmp);
    }
    return 0;
}

static PyMemberDef Custom_members[] = {
    {"number", T_INT, offsetof(CustomObject, number), 0,
     "custom number"},
    {NULL}  /* Sentinel */
};

static PyObject *
Custom_getfirst(CustomObject *self, void *closure)
{
    Py_INCREF(self->first);
    return self->first;
}

static int
Custom_setfirst(CustomObject *self, PyObject *value, void *closure)
{
    PyObject *tmp;
    if (value == NULL) {
        PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");
        return -1;
    }
    if (!PyUnicode_Check(value)) {
        PyErr_SetString(PyExc_TypeError,
                        "The first attribute value must be a string");
        return -1;
    }
    tmp = self->first;
    Py_INCREF(value);
    self->first = value;
    Py_DECREF(tmp);
    return 0;
}

static PyObject *
Custom_getlast(CustomObject *self, void *closure)
{
    Py_INCREF(self->last);
    return self->last;
}

static int
Custom_setlast(CustomObject *self, PyObject *value, void *closure)
{
    PyObject *tmp;
    if (value == NULL) {
        PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");
        return -1;
    }
    if (!PyUnicode_Check(value)) {
        PyErr_SetString(PyExc_TypeError,
                        "The last attribute value must be a string");
        return -1;
    }
    tmp = self->last;
    Py_INCREF(value);
    self->last = value;
    Py_DECREF(tmp);
    return 0;
}

static PyGetSetDef Custom_getsetters[] = {
    {"first", (getter) Custom_getfirst, (setter) Custom_setfirst,
     "first name", NULL},
    {"last", (getter) Custom_getlast, (setter) Custom_setlast,
     "last name", NULL},
    {NULL}  /* Sentinel */
};

static PyObject *
Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))
{
    return PyUnicode_FromFormat("%S %S", self->first, self->last);
}

static PyMethodDef Custom_methods[] = {
    {"name", (PyCFunction) Custom_name, METH_NOARGS,
     "Return the name, combining the first and last name"
    },
    {NULL}  /* Sentinel */
};

static PyTypeObject CustomType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "custom3.Custom",
    .tp_doc = "Custom objects",
    .tp_basicsize = sizeof(CustomObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
    .tp_new = Custom_new,
    .tp_init = (initproc) Custom_init,
    .tp_dealloc = (destructor) Custom_dealloc,
    .tp_members = Custom_members,
    .tp_methods = Custom_methods,
    .tp_getset = Custom_getsetters,
};

static PyModuleDef custommodule = {
    PyModuleDef_HEAD_INIT,
    .m_name = "custom3",
    .m_doc = "Example module that creates an extension type.",
    .m_size = -1,
};

PyMODINIT_FUNC
PyInit_custom3(void)
{
    PyObject *m;
    if (PyType_Ready(&CustomType) < 0)
        return NULL;

    m = PyModule_Create(&custommodule);
    if (m == NULL)
        return NULL;

    Py_INCREF(&CustomType);
    if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {
        Py_DECREF(&CustomType);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}

first属性とlast属性をより細かく制御するために、カスタムのゲッター関数とセッター関数を使用します。 first属性を取得および設定するための関数は次のとおりです。

static PyObject *
Custom_getfirst(CustomObject *self, void *closure)
{
    Py_INCREF(self->first);
    return self->first;
}

static int
Custom_setfirst(CustomObject *self, PyObject *value, void *closure)
{
    PyObject *tmp;
    if (value == NULL) {
        PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");
        return -1;
    }
    if (!PyUnicode_Check(value)) {
        PyErr_SetString(PyExc_TypeError,
                        "The first attribute value must be a string");
        return -1;
    }
    tmp = self->first;
    Py_INCREF(value);
    self->first = value;
    Py_DECREF(tmp);
    return 0;
}

getter関数には、Customオブジェクトと、voidポインターである「クロージャ」が渡されます。 この場合、クロージャは無視されます。 (クロージャーは、定義データがゲッターとセッターに渡される高度な使用法をサポートします。 これは、たとえば、クロージャー内のデータに基づいて属性を取得または設定することを決定するゲッター関数とセッター関数の単一のセットを許可するために使用できます。)

セッター関数には、Customオブジェクト、新しい値、およびクロージャーが渡されます。 新しい値はNULLの場合があり、その場合、属性は削除されます。 セッターでは、属性が削除された場合、またはその新しい値が文字列でない場合にエラーが発生します。

PyGetSetDef 構造体の配列を作成します。

static PyGetSetDef Custom_getsetters[] = {
    {"first", (getter) Custom_getfirst, (setter) Custom_setfirst,
     "first name", NULL},
    {"last", (getter) Custom_getlast, (setter) Custom_setlast,
     "last name", NULL},
    {NULL}  /* Sentinel */
};

tp_getset スロットに登録します。

.tp_getset = Custom_getsetters,

PyGetSetDef 構造体の最後の項目は、上記の「クロージャー」です。 この場合、クロージャを使用していないため、NULLを渡すだけです。

これらの属性のメンバー定義も削除します。

static PyMemberDef Custom_members[] = {
    {"number", T_INT, offsetof(CustomObject, number), 0,
     "custom number"},
    {NULL}  /* Sentinel */
};

また、 tp_init ハンドラーを更新して、文字列 3 のみを渡すことができるようにする必要があります。

static int
Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"first", "last", "number", NULL};
    PyObject *first = NULL, *last = NULL, *tmp;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,
                                     &first, &last,
                                     &self->number))
        return -1;

    if (first) {
        tmp = self->first;
        Py_INCREF(first);
        self->first = first;
        Py_DECREF(tmp);
    }
    if (last) {
        tmp = self->last;
        Py_INCREF(last);
        self->last = last;
        Py_DECREF(tmp);
    }
    return 0;
}

これらの変更により、firstおよびlastメンバーがNULLになることはないため、ほとんどの場合、NULL値のチェックを削除できます。 これは、ほとんどの Py_XDECREF()呼び出しを Py_DECREF()呼び出しに変換できることを意味します。 これらの呼び出しを変更できない唯一の場所は、tp_dealloc実装であり、tp_newでこれらのメンバーの初期化が失敗した可能性があります。

また、以前と同じように、初期化関数のモジュール初期化関数とモジュール名の名前を変更し、setup.pyファイルに追加の定義を追加します。


2.4。 周期的なガベージコレクションのサポート

Pythonにはサイクリックガベージコレクター(GC)があり、参照カウントがゼロでない場合でも不要なオブジェクトを識別できます。 これは、オブジェクトがサイクルに関与している場合に発生する可能性があります。 たとえば、次のことを考慮してください。

>>> l = []
>>> l.append(l)
>>> del l

この例では、それ自体を含むリストを作成します。 削除しても、それ自体からの参照が残っています。 その参照カウントはゼロになりません。 幸い、Pythonの循環ガベージコレクターは、最終的にリストがガベージであることを認識し、それを解放します。

Customの例の2番目のバージョンでは、あらゆる種類のオブジェクトをfirstまたはlast属性 4 に格納できるようにしました。 さらに、2番目と3番目のバージョンでは、サブクラス化Customを許可し、サブクラスは任意の属性を追加できます。 これら2つの理由のいずれかにより、Customオブジェクトはサイクルに参加できます。

>>> import custom3
>>> class Derived(custom3.Custom): pass
...
>>> n = Derived()
>>> n.some_attribute = n

参照サイクルに参加しているCustomインスタンスがサイクリックGCによって適切に検出および収集されるようにするには、Customタイプで2つの追加スロットを埋め、これらのスロットを有効にするフラグを有効にする必要があります。

#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "structmember.h"

typedef struct {
    PyObject_HEAD
    PyObject *first; /* first name */
    PyObject *last;  /* last name */
    int number;
} CustomObject;

static int
Custom_traverse(CustomObject *self, visitproc visit, void *arg)
{
    Py_VISIT(self->first);
    Py_VISIT(self->last);
    return 0;
}

static int
Custom_clear(CustomObject *self)
{
    Py_CLEAR(self->first);
    Py_CLEAR(self->last);
    return 0;
}

static void
Custom_dealloc(CustomObject *self)
{
    PyObject_GC_UnTrack(self);
    Custom_clear(self);
    Py_TYPE(self)->tp_free((PyObject *) self);
}

static PyObject *
Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    CustomObject *self;
    self = (CustomObject *) type->tp_alloc(type, 0);
    if (self != NULL) {
        self->first = PyUnicode_FromString("");
        if (self->first == NULL) {
            Py_DECREF(self);
            return NULL;
        }
        self->last = PyUnicode_FromString("");
        if (self->last == NULL) {
            Py_DECREF(self);
            return NULL;
        }
        self->number = 0;
    }
    return (PyObject *) self;
}

static int
Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"first", "last", "number", NULL};
    PyObject *first = NULL, *last = NULL, *tmp;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,
                                     &first, &last,
                                     &self->number))
        return -1;

    if (first) {
        tmp = self->first;
        Py_INCREF(first);
        self->first = first;
        Py_DECREF(tmp);
    }
    if (last) {
        tmp = self->last;
        Py_INCREF(last);
        self->last = last;
        Py_DECREF(tmp);
    }
    return 0;
}

static PyMemberDef Custom_members[] = {
    {"number", T_INT, offsetof(CustomObject, number), 0,
     "custom number"},
    {NULL}  /* Sentinel */
};

static PyObject *
Custom_getfirst(CustomObject *self, void *closure)
{
    Py_INCREF(self->first);
    return self->first;
}

static int
Custom_setfirst(CustomObject *self, PyObject *value, void *closure)
{
    if (value == NULL) {
        PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");
        return -1;
    }
    if (!PyUnicode_Check(value)) {
        PyErr_SetString(PyExc_TypeError,
                        "The first attribute value must be a string");
        return -1;
    }
    Py_INCREF(value);
    Py_CLEAR(self->first);
    self->first = value;
    return 0;
}

static PyObject *
Custom_getlast(CustomObject *self, void *closure)
{
    Py_INCREF(self->last);
    return self->last;
}

static int
Custom_setlast(CustomObject *self, PyObject *value, void *closure)
{
    if (value == NULL) {
        PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");
        return -1;
    }
    if (!PyUnicode_Check(value)) {
        PyErr_SetString(PyExc_TypeError,
                        "The last attribute value must be a string");
        return -1;
    }
    Py_INCREF(value);
    Py_CLEAR(self->last);
    self->last = value;
    return 0;
}

static PyGetSetDef Custom_getsetters[] = {
    {"first", (getter) Custom_getfirst, (setter) Custom_setfirst,
     "first name", NULL},
    {"last", (getter) Custom_getlast, (setter) Custom_setlast,
     "last name", NULL},
    {NULL}  /* Sentinel */
};

static PyObject *
Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))
{
    return PyUnicode_FromFormat("%S %S", self->first, self->last);
}

static PyMethodDef Custom_methods[] = {
    {"name", (PyCFunction) Custom_name, METH_NOARGS,
     "Return the name, combining the first and last name"
    },
    {NULL}  /* Sentinel */
};

static PyTypeObject CustomType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "custom4.Custom",
    .tp_doc = "Custom objects",
    .tp_basicsize = sizeof(CustomObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
    .tp_new = Custom_new,
    .tp_init = (initproc) Custom_init,
    .tp_dealloc = (destructor) Custom_dealloc,
    .tp_traverse = (traverseproc) Custom_traverse,
    .tp_clear = (inquiry) Custom_clear,
    .tp_members = Custom_members,
    .tp_methods = Custom_methods,
    .tp_getset = Custom_getsetters,
};

static PyModuleDef custommodule = {
    PyModuleDef_HEAD_INIT,
    .m_name = "custom4",
    .m_doc = "Example module that creates an extension type.",
    .m_size = -1,
};

PyMODINIT_FUNC
PyInit_custom4(void)
{
    PyObject *m;
    if (PyType_Ready(&CustomType) < 0)
        return NULL;

    m = PyModule_Create(&custommodule);
    if (m == NULL)
        return NULL;

    Py_INCREF(&CustomType);
    if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {
        Py_DECREF(&CustomType);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}

まず、トラバーサルメソッドにより、サイクリックGCは、サイクルに参加できるサブオブジェクトについて知ることができます。

static int
Custom_traverse(CustomObject *self, visitproc visit, void *arg)
{
    int vret;
    if (self->first) {
        vret = visit(self->first, arg);
        if (vret != 0)
            return vret;
    }
    if (self->last) {
        vret = visit(self->last, arg);
        if (vret != 0)
            return vret;
    }
    return 0;
}

サイクルに参加できるサブオブジェクトごとに、トラバーサルメソッドに渡されるvisit()関数を呼び出す必要があります。 visit()関数は、引数としてサブオブジェクトと、トラバーサルメソッドに渡された追加の引数 arg を取ります。 ゼロ以外の場合に返される必要のある整数値を返します。

Pythonは、訪問関数の呼び出しを自動化する Py_VISIT()マクロを提供します。 Py_VISIT()を使用すると、Custom_traverseの定型文の量を最小限に抑えることができます。

static int
Custom_traverse(CustomObject *self, visitproc visit, void *arg)
{
    Py_VISIT(self->first);
    Py_VISIT(self->last);
    return 0;
}

ノート

tp_traverse 実装は、 Py_VISIT()を使用するために、引数に正確に visit および arg という名前を付ける必要があります。


次に、サイクルに参加できるサブオブジェクトをクリアするためのメソッドを提供する必要があります。

static int
Custom_clear(CustomObject *self)
{
    Py_CLEAR(self->first);
    Py_CLEAR(self->last);
    return 0;
}

Py_CLEAR()マクロの使用に注意してください。 これは、参照カウントを減らしながら、任意のタイプのデータ属性をクリアするための推奨される安全な方法です。 属性をNULLに設定する前に、代わりに Py_XDECREF()を呼び出すと、属性のデストラクタが属性を再度読み取るコードを呼び出す可能性があります([X212X ]特に参照サイクルがある場合)。

ノート

Py_CLEAR()をエミュレートするには、次のように記述します。

PyObject *tmp;
tmp = self->first;
self->first = NULL;
Py_XDECREF(tmp);

それでも、属性を削除するときに常に Py_CLEAR()を使用する方がはるかに簡単で、エラーが発生しにくくなります。 堅牢性を犠牲にしてマイクロ最適化しようとしないでください!


デアロケータCustom_deallocは、属性をクリアするときに任意のコードを呼び出す可能性があります。 これは、関数内で循環GCをトリガーできることを意味します。 GCは参照カウントがゼロではないと想定しているため、メンバーをクリアする前に PyObject_GC_UnTrack()を呼び出してGCからオブジェクトを追跡解除する必要があります。 PyObject_GC_UnTrack()およびCustom_clearを使用して再実装されたデアロケーターは次のとおりです。

static void
Custom_dealloc(CustomObject *self)
{
    PyObject_GC_UnTrack(self);
    Custom_clear(self);
    Py_TYPE(self)->tp_free((PyObject *) self);
}

最後に、 Py_TPFLAGS_HAVE_GC フラグをクラスフラグに追加します。

.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,

それはほとんどそれです。 カスタム tp_alloc または tp_free ハンドラーを作成した場合は、循環ガベージコレクション用にそれらを変更する必要があります。 ほとんどの拡張機能は、自動的に提供されるバージョンを使用します。


2.5。 他のタイプのサブクラス化

既存のタイプから派生した新しい拡張タイプを作成することが可能です。 拡張機能は必要な PyTypeObject を簡単に使用できるため、組み込み型から継承するのが最も簡単です。 これらの PyTypeObject 構造を拡張モジュール間で共有するのは難しい場合があります。

この例では、組み込みの list タイプを継承するSubListタイプを作成します。 新しいタイプは通常のリストと完全に互換性がありますが、内部カウンターを増やす追加のincrement()メソッドがあります。

>>> import sublist
>>> s = sublist.SubList(range(3))
>>> s.extend(s)
>>> print(len(s))
6
>>> print(s.increment())
1
>>> print(s.increment())
2
#define PY_SSIZE_T_CLEAN
#include <Python.h>

typedef struct {
    PyListObject list;
    int state;
} SubListObject;

static PyObject *
SubList_increment(SubListObject *self, PyObject *unused)
{
    self->state++;
    return PyLong_FromLong(self->state);
}

static PyMethodDef SubList_methods[] = {
    {"increment", (PyCFunction) SubList_increment, METH_NOARGS,
     PyDoc_STR("increment state counter")},
    {NULL},
};

static int
SubList_init(SubListObject *self, PyObject *args, PyObject *kwds)
{
    if (PyList_Type.tp_init((PyObject *) self, args, kwds) < 0)
        return -1;
    self->state = 0;
    return 0;
}

static PyTypeObject SubListType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "sublist.SubList",
    .tp_doc = "SubList objects",
    .tp_basicsize = sizeof(SubListObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
    .tp_init = (initproc) SubList_init,
    .tp_methods = SubList_methods,
};

static PyModuleDef sublistmodule = {
    PyModuleDef_HEAD_INIT,
    .m_name = "sublist",
    .m_doc = "Example module that creates an extension type.",
    .m_size = -1,
};

PyMODINIT_FUNC
PyInit_sublist(void)
{
    PyObject *m;
    SubListType.tp_base = &PyList_Type;
    if (PyType_Ready(&SubListType) < 0)
        return NULL;

    m = PyModule_Create(&sublistmodule);
    if (m == NULL)
        return NULL;

    Py_INCREF(&SubListType);
    if (PyModule_AddObject(m, "SubList", (PyObject *) &SubListType) < 0) {
        Py_DECREF(&SubListType);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}

ご覧のとおり、ソースコードは前のセクションのCustomの例によく似ています。 それらの主な違いを分析します。

typedef struct {
    PyListObject list;
    int state;
} SubListObject;

派生型オブジェクトの主な違いは、基本型のオブジェクト構造が最初の値でなければならないことです。 基本タイプには、構造の先頭に PyObject_HEAD()がすでに含まれています。

PythonオブジェクトがSubListインスタンスの場合、そのPyObject *ポインターはPyListObject *SubListObject *の両方に安全にキャストできます。

static int
SubList_init(SubListObject *self, PyObject *args, PyObject *kwds)
{
    if (PyList_Type.tp_init((PyObject *) self, args, kwds) < 0)
        return -1;
    self->state = 0;
    return 0;
}

基本型の__init__メソッドを呼び出す方法は上記のとおりです。

このパターンは、カスタム tp_new および tp_dealloc メンバーを使用して型を作成する場合に重要です。 tp_new ハンドラーは、 tp_alloc を使用してオブジェクトのメモリを実際に作成するのではなく、基本クラスに独自の tp_new を呼び出して処理させます。

PyTypeObject 構造体は、型の具象基本クラスを指定する tp_base をサポートします。 クロスプラットフォームコンパイラの問題により、 PyList_Type への参照をそのフィールドに直接入力することはできません。 これは、モジュール初期化関数の後半で実行する必要があります。

PyMODINIT_FUNC
PyInit_sublist(void)
{
    PyObject* m;
    SubListType.tp_base = &PyList_Type;
    if (PyType_Ready(&SubListType) < 0)
        return NULL;

    m = PyModule_Create(&sublistmodule);
    if (m == NULL)
        return NULL;

    Py_INCREF(&SubListType);
    if (PyModule_AddObject(m, "SubList", (PyObject *) &SubListType) < 0) {
        Py_DECREF(&SubListType);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}

PyType_Ready()を呼び出す前に、型構造体に tp_base スロットを埋める必要があります。 既存の型を導出する場合、 tp_alloc スロットに PyType_GenericNew()を入力する必要はありません。基本型からの割り当て関数が継承されます。

その後、 PyType_Ready()を呼び出して、型オブジェクトをモジュールに追加することは、基本的なCustomの例と同じです。

脚注

1
これは、オブジェクトが文字列や浮動小数点数などの基本型であることがわかっている場合に当てはまります。
2
この例では、 tp_dealloc ハンドラーでこれに依存しました。これは、このタイプがガベージコレクションをサポートしていないためです。
3
最初と最後のメンバーが文字列であることがわかったので、参照カウントを減らすことにあまり注意を払わないかもしれませんが、文字列サブクラスのインスタンスを受け入れます。 通常の文字列の割り当てを解除してもオブジェクトが呼び出されない場合でも、文字列サブクラスのインスタンスの割り当てを解除してもオブジェクトが呼び出されないことを保証することはできません。
4
また、属性が文字列インスタンスに制限されている場合でも、ユーザーは任意の str サブクラスを渡すことができるため、参照サイクルを作成できます。