2. 新しいタイプの定義—Pythonドキュメント

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

2.2。 新しいタイプの定義

前の章で述べたように、Pythonを使用すると、拡張モジュールの作成者は、コアPythonの文字列やリストのように、Pythonコードから操作できる新しい型を定義できます。

これは難しいことではありません。 すべての拡張タイプのコードはパターンに従いますが、開始する前に理解する必要のある詳細がいくつかあります。

ノート

Python 2.2では、新しい型の定義方法が劇的に(そしてより良く)変更されました。 このドキュメントでは、Python2.2以降の新しい型を定義する方法について説明します。 古いバージョンのPythonをサポートする必要がある場合は、このドキュメントの古いバージョンを参照する必要があります。


2.1。 基礎

Pythonランタイムは、すべてのPythonオブジェクトをPyObject*型の変数として認識します。 PyObject は、それほど壮大なオブジェクトではありません。refcountと、オブジェクトの「タイプオブジェクト」へのポインタが含まれているだけです。 これがアクションの場所です。 タイプオブジェクトは、たとえば、属性がオブジェクトで検索されたとき、または別のオブジェクトで乗算されたときに呼び出される(C)関数を決定します。 これらのC関数は「タイプメソッド」と呼ばれます。

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

この種のことは例によってのみ説明できるので、ここに新しいタイプを定義する最小限の、しかし完全なモジュールがあります:

#include <Python.h>

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

static PyTypeObject noddy_NoddyType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "noddy.Noddy",             /* tp_name */
    sizeof(noddy_NoddyObject), /* tp_basicsize */
    0,                         /* tp_itemsize */
    0,                         /* tp_dealloc */
    0,                         /* tp_print */
    0,                         /* tp_getattr */
    0,                         /* tp_setattr */
    0,                         /* tp_compare */
    0,                         /* tp_repr */
    0,                         /* tp_as_number */
    0,                         /* tp_as_sequence */
    0,                         /* tp_as_mapping */
    0,                         /* tp_hash */
    0,                         /* tp_call */
    0,                         /* tp_str */
    0,                         /* tp_getattro */
    0,                         /* tp_setattro */
    0,                         /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT,        /* tp_flags */
    "Noddy objects",           /* tp_doc */
};

static PyMethodDef noddy_methods[] = {
    {NULL}  /* Sentinel */
};

#ifndef PyMODINIT_FUNC  /* declarations for DLL import/export */
#define PyMODINIT_FUNC void
#endif
PyMODINIT_FUNC
initnoddy(void) 
{
    PyObject* m;

    noddy_NoddyType.tp_new = PyType_GenericNew;
    if (PyType_Ready(&noddy_NoddyType) < 0)
        return;

    m = Py_InitModule3("noddy", noddy_methods,
                       "Example module that creates an extension type.");

    Py_INCREF(&noddy_NoddyType);
    PyModule_AddObject(m, "Noddy", (PyObject *)&noddy_NoddyType);
}

さて、それは一度に理解するのにかなりの量ですが、うまくいけば、ビットは前の章からおなじみのように見えるでしょう。

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

typedef struct {
    PyObject_HEAD
} noddy_NoddyObject;

これは、Noddyオブジェクトに含まれるものです。この場合、すべてのPythonオブジェクトに含まれるのは、refcountと型オブジェクトへのポインターだけです。 これらは、PyObject_HEADマクロが取り込むフィールドです。 マクロの理由は、レイアウトを標準化し、デバッグビルドで特別なデバッグフィールドを有効にするためです。 PyObject_HEADマクロの後にセミコロンがないことに注意してください。 1つはマクロ定義に含まれています。 誤って追加することには注意してください。 習慣から行うのは簡単で、コンパイラは文句を言わないかもしれませんが、他の誰かがおそらく文句を言うでしょう! (Windowsでは、MSVCはこれをエラーと呼び、コードのコンパイルを拒否することが知られています。)

対照的に、標準のPython整数の対応する定義を見てみましょう。

typedef struct {
    PyObject_HEAD
    long ob_ival;
} PyIntObject;

次に進むと、型オブジェクトという問題が発生します。

static PyTypeObject noddy_NoddyType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "noddy.Noddy",             /* tp_name */
    sizeof(noddy_NoddyObject), /* tp_basicsize */
    0,                         /* tp_itemsize */
    0,                         /* tp_dealloc */
    0,                         /* tp_print */
    0,                         /* tp_getattr */
    0,                         /* tp_setattr */
    0,                         /* tp_compare */
    0,                         /* tp_repr */
    0,                         /* tp_as_number */
    0,                         /* tp_as_sequence */
    0,                         /* tp_as_mapping */
    0,                         /* tp_hash */
    0,                         /* tp_call */
    0,                         /* tp_str */
    0,                         /* tp_getattro */
    0,                         /* tp_setattro */
    0,                         /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT,        /* tp_flags */
    "Noddy objects",           /* tp_doc */
};

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

これは非常に重要なので、その上部をさらに分解します。

PyVarObject_HEAD_INIT(NULL, 0)

この行は少し疣贅です。 私たちが書きたいのは:

PyVarObject_HEAD_INIT(&PyType_Type, 0)

型オブジェクトの型は「type」ですが、これは厳密にCに準拠しておらず、一部のコンパイラは文句を言います。 幸い、このメンバーは PyType_Ready()によって入力されます。

"noddy.Noddy",              /* tp_name */

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

>>> "" + noddy.new_noddy()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: cannot add type "noddy.Noddy" to string

名前は、モジュール名とモジュール内のタイプの名前の両方を含む点線の名前であることに注意してください。 この場合のモジュールはnoddyで、タイプはNoddyなので、タイプ名をnoddy.Noddyに設定します。 ドットのない名前を使用することの1つの副作用は、pydocドキュメントツールがモジュールドキュメントに新しいタイプをリストしないことです。

sizeof(noddy_NoddyObject),  /* tp_basicsize */

これは、 PyObject_New()を呼び出したときにPythonが割り当てるメモリの量を認識できるようにするためです。

ノート

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


0,                          /* tp_itemsize */

これは、リストや文字列などの可変長オブジェクトと関係があります。 今はこれを無視してください。

提供していない多くの型メソッドをスキップして、クラスフラグを Py_TPFLAGS_DEFAULT に設定します。

Py_TPFLAGS_DEFAULT,        /* tp_flags */

すべてのタイプは、フラグにこの定数を含める必要があります。 これにより、現在のバージョンのPythonで定義されているすべてのメンバーが有効になります。

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

"Noddy objects",           /* tp_doc */

次に、タイプメソッド、つまりオブジェクトを他のオブジェクトとは異なるものにするものについて説明します。 このバージョンのモジュールでは、これらのいずれも実装しません。 この例を後で拡張して、より興味深い動作を実現します。

今のところ、私たちができることは、新しいNoddyオブジェクトを作成することだけです。 オブジェクトの作成を有効にするには、 tp_new 実装を提供する必要があります。 この場合、API関数 PyType_GenericNew()によって提供されるデフォルトの実装を使用できます。 これを tp_new スロットに割り当てたいのですが、移植性のために、一部のプラットフォームまたはコンパイラでは、別のプラットフォームで定義された関数で構造体メンバーを静的に初期化できません。 Cモジュールなので、代わりに、 PyType_Ready()を呼び出す直前に、モジュール初期化関数で tp_new スロットを割り当てます。

noddy_NoddyType.tp_new = PyType_GenericNew;
if (PyType_Ready(&noddy_NoddyType) < 0)
    return;

他のすべての型メソッドは NULL なので、後で説明します—これは後のセクションで説明します。

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

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

これにより、Noddyタイプが初期化され、最初に NULL に設定したob_typeを含む多数のメンバーがファイリングされます。

PyModule_AddObject(m, "Noddy", (PyObject *)&noddy_NoddyType);

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

>>> import noddy
>>> mynoddy = noddy.Noddy()

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

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

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

$ python setup.py build

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

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

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

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

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

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

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

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

static PyObject *
Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    Noddy *self;

    self = (Noddy *)type->tp_alloc(type, 0);
    if (self != NULL) {
        self->first = PyString_FromString("");
        if (self->first == NULL) {
            Py_DECREF(self);
            return NULL;
        }

        self->last = PyString_FromString("");
        if (self->last == NULL) {
            Py_DECREF(self);
            return NULL;
        }

        self->number = 0;
    }

    return (PyObject *)self;
}

static int
Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)
{
    PyObject *first=NULL, *last=NULL, *tmp;

    static char *kwlist[] = {"first", "last", "number", NULL};

    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 Noddy_members[] = {
    {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,
     "first name"},
    {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,
     "last name"},
    {"number", T_INT, offsetof(Noddy, number), 0,
     "noddy number"},
    {NULL}  /* Sentinel */
};

static PyObject *
Noddy_name(Noddy* self)
{
    static PyObject *format = NULL;
    PyObject *args, *result;

    if (format == NULL) {
        format = PyString_FromString("%s %s");
        if (format == NULL)
            return NULL;
    }

    if (self->first == NULL) {
        PyErr_SetString(PyExc_AttributeError, "first");
        return NULL;
    }

    if (self->last == NULL) {
        PyErr_SetString(PyExc_AttributeError, "last");
        return NULL;
    }

    args = Py_BuildValue("OO", self->first, self->last);
    if (args == NULL)
        return NULL;

    result = PyString_Format(format, args);
    Py_DECREF(args);

    return result;
}

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

static PyTypeObject NoddyType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "noddy.Noddy",             /* tp_name */
    sizeof(Noddy),             /* tp_basicsize */
    0,                         /* tp_itemsize */
    (destructor)Noddy_dealloc, /* tp_dealloc */
    0,                         /* tp_print */
    0,                         /* tp_getattr */
    0,                         /* tp_setattr */
    0,                         /* tp_compare */
    0,                         /* tp_repr */
    0,                         /* tp_as_number */
    0,                         /* tp_as_sequence */
    0,                         /* tp_as_mapping */
    0,                         /* tp_hash */
    0,                         /* tp_call */
    0,                         /* tp_str */
    0,                         /* tp_getattro */
    0,                         /* tp_setattro */
    0,                         /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT |
        Py_TPFLAGS_BASETYPE,   /* tp_flags */
    "Noddy objects",           /* tp_doc */
    0,                         /* tp_traverse */
    0,                         /* tp_clear */
    0,                         /* tp_richcompare */
    0,                         /* tp_weaklistoffset */
    0,                         /* tp_iter */
    0,                         /* tp_iternext */
    Noddy_methods,             /* tp_methods */
    Noddy_members,             /* tp_members */
    0,                         /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    (initproc)Noddy_init,      /* tp_init */
    0,                         /* tp_alloc */
    Noddy_new,                 /* tp_new */
};

static PyMethodDef module_methods[] = {
    {NULL}  /* Sentinel */
};

#ifndef PyMODINIT_FUNC  /* declarations for DLL import/export */
#define PyMODINIT_FUNC void
#endif
PyMODINIT_FUNC
initnoddy2(void)
{
    PyObject* m;

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

    m = Py_InitModule3("noddy2", module_methods,
                       "Example module that creates an extension type.");

    if (m == NULL)
        return;

    Py_INCREF(&NoddyType);
    PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);
}

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

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

#include <structmember.h>

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

Noddyオブジェクト構造の名前がNoddyに短縮されました。 タイプオブジェクト名はNoddyTypeに短縮されました。

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

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

typedef struct {
    PyObject_HEAD
    PyObject *first;
    PyObject *last;
    int number;
} Noddy;

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

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

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

(destructor)Noddy_dealloc, /*tp_dealloc*/

このメソッドは、2つのPython属性の参照カウントを減らします。 firstおよびlastメンバーは NULL である可能性があるため、ここでは Py_XDECREF()を使用します。 次に、オブジェクトのタイプの tp_free メンバーを呼び出して、オブジェクトのメモリを解放します。 オブジェクトはサブクラスのインスタンスである可能性があるため、オブジェクトのタイプはNoddyTypeではない可能性があることに注意してください。

名前と名前が空の文字列に初期化されていることを確認したいので、新しいメソッドを提供します。

static PyObject *
Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    Noddy *self;

    self = (Noddy *)type->tp_alloc(type, 0);
    if (self != NULL) {
        self->first = PyString_FromString("");
        if (self->first == NULL)
          {
            Py_DECREF(self);
            return NULL;
          }

        self->last = PyString_FromString("");
        if (self->last == NULL)
          {
            Py_DECREF(self);
            return NULL;
          }

        self->number = 0;
    }

    return (PyObject *)self;
}

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

Noddy_new,                 /* tp_new */

新しいメンバーは、そのタイプのオブジェクトを(初期化するのではなく)作成する責任があります。 Pythonでは__new__()メソッドとして公開されています。 __new__()メソッドの詳細については、「Pythonでの型とクラスの統合」というタイトルの論文を参照してください。 新しいメソッドを実装する理由の1つは、インスタンス変数の初期値を保証することです。 この場合、新しいメソッドを使用して、メンバーfirstおよびlastの初期値が NULL でないことを確認します。 初期値が NULL であるかどうかを気にしない場合は、以前と同様に、 PyType_GenericNew()を新しいメソッドとして使用できます。 PyType_GenericNew()は、すべてのインスタンス変数メンバーを NULL に初期化します。

newメソッドは、インスタンス化される型と型が呼び出されたときに渡される引数が渡される静的メソッドであり、作成された新しいオブジェクトを返します。 新しいメソッドは常に位置引数とキーワード引数を受け入れますが、多くの場合、引数を無視し、引数の処理を初期化メソッドに任せます。 タイプがサブクラス化をサポートしている場合、渡されるタイプは定義されているタイプではない可能性があることに注意してください。 新しいメソッドは、tp_allocスロットを呼び出してメモリを割り当てます。 tp_alloc スロットを自分で埋めることはありません。 むしろ、 PyType_Ready()は、基本クラス(デフォルトではオブジェクト)から継承することで、それを埋めます。 ほとんどのタイプはデフォルトの割り当てを使用します。

ノート

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


初期化関数を提供します:

static int
Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)
{
    PyObject *first=NULL, *last=NULL, *tmp;

    static char *kwlist[] = {"first", "last", "number", NULL};

    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 スロットを埋めることによって。

(initproc)Noddy_init,         /* tp_init */

tp_init スロットは、Pythonでは__init__()メソッドとして公開されています。 作成後にオブジェクトを初期化するために使用されます。 新しいメソッドとは異なり、イニシャライザが呼び出されることを保証することはできません。 イニシャライザは、オブジェクトの選択を解除するときに呼び出されず、オーバーライドできます。 イニシャライザは引数を受け入れて、インスタンスの初期値を提供します。 イニシャライザーは常に位置引数とキーワード引数を受け入れます。

イニシャライザーは複数回呼び出すことができます。 オブジェクトに対して誰でも__init__()メソッドを呼び出すことができます。 このため、新しい値を割り当てるときは特に注意する必要があります。 たとえば、firstメンバーを次のように割り当てたいと思うかもしれません。

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

しかし、これは危険です。 私たちのタイプはfirstメンバーのタイプを制限しないので、どんな種類のオブジェクトでもかまいません。 firstメンバーにアクセスしようとするコードを実行させるデストラクタが含まれている可能性があります。 偏執的であり、この可能性から身を守るために、ほとんどの場合、参照数を減らす前にメンバーを再割り当てします。 いつこれをしなければならないのですか?

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

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

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

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

Noddy_members,             /* tp_members */

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

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

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

static PyObject *
Noddy_name(Noddy* self)
{
    static PyObject *format = NULL;
    PyObject *args, *result;

    if (format == NULL) {
        format = PyString_FromString("%s %s");
        if (format == NULL)
            return NULL;
    }

    if (self->first == NULL) {
        PyErr_SetString(PyExc_AttributeError, "first");
        return NULL;
    }

    if (self->last == NULL) {
        PyErr_SetString(PyExc_AttributeError, "last");
        return NULL;
    }

    args = Py_BuildValue("OO", self->first, self->last);
    if (args == NULL)
        return NULL;

    result = PyString_Format(format, args);
    Py_DECREF(args);

    return result;
}

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

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

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

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

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

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

Noddy_methods,             /* tp_methods */

METH_NOARGS フラグを使用して、メソッドに引数が渡されないことを示していることに注意してください。

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

Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/

initnoddy()の名前をinitnoddy2()に変更し、 Py_InitModule3()に渡されたモジュール名を更新します。

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

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

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

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

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

typedef struct {
    PyObject_HEAD
    PyObject *first;
    PyObject *last;
    int number;
} Noddy;

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

static PyObject *
Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    Noddy *self;

    self = (Noddy *)type->tp_alloc(type, 0);
    if (self != NULL) {
        self->first = PyString_FromString("");
        if (self->first == NULL) {
            Py_DECREF(self);
            return NULL;
        }

        self->last = PyString_FromString("");
        if (self->last == NULL) {
            Py_DECREF(self);
            return NULL;
        }

        self->number = 0;
    }

    return (PyObject *)self;
}

static int
Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)
{
    PyObject *first=NULL, *last=NULL, *tmp;

    static char *kwlist[] = {"first", "last", "number", NULL};

    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|SSi", 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 Noddy_members[] = {
    {"number", T_INT, offsetof(Noddy, number), 0,
     "noddy number"},
    {NULL}  /* Sentinel */
};

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

static int
Noddy_setfirst(Noddy *self, PyObject *value, void *closure)
{
    if (value == NULL) {
        PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");
        return -1;
    }

    if (! PyString_Check(value)) {
        PyErr_SetString(PyExc_TypeError,
                        "The first attribute value must be a string");
        return -1;
    }

    Py_DECREF(self->first);
    Py_INCREF(value);
    self->first = value;

    return 0;
}

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

static int
Noddy_setlast(Noddy *self, PyObject *value, void *closure)
{
    if (value == NULL) {
        PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");
        return -1;
    }

    if (! PyString_Check(value)) {
        PyErr_SetString(PyExc_TypeError,
                        "The last attribute value must be a string");
        return -1;
    }

    Py_DECREF(self->last);
    Py_INCREF(value);
    self->last = value;

    return 0;
}

static PyGetSetDef Noddy_getseters[] = {
    {"first",
     (getter)Noddy_getfirst, (setter)Noddy_setfirst,
     "first name",
     NULL},
    {"last",
     (getter)Noddy_getlast, (setter)Noddy_setlast,
     "last name",
     NULL},
    {NULL}  /* Sentinel */
};

static PyObject *
Noddy_name(Noddy* self)
{
    static PyObject *format = NULL;
    PyObject *args, *result;

    if (format == NULL) {
        format = PyString_FromString("%s %s");
        if (format == NULL)
            return NULL;
    }

    args = Py_BuildValue("OO", self->first, self->last);
    if (args == NULL)
        return NULL;

    result = PyString_Format(format, args);
    Py_DECREF(args);

    return result;
}

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

static PyTypeObject NoddyType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "noddy.Noddy",             /* tp_name */
    sizeof(Noddy),             /* tp_basicsize */
    0,                         /* tp_itemsize */
    (destructor)Noddy_dealloc, /* tp_dealloc */
    0,                         /* tp_print */
    0,                         /* tp_getattr */
    0,                         /* tp_setattr */
    0,                         /* tp_compare */
    0,                         /* tp_repr */
    0,                         /* tp_as_number */
    0,                         /* tp_as_sequence */
    0,                         /* tp_as_mapping */
    0,                         /* tp_hash */
    0,                         /* tp_call */
    0,                         /* tp_str */
    0,                         /* tp_getattro */
    0,                         /* tp_setattro */
    0,                         /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT |
        Py_TPFLAGS_BASETYPE,   /* tp_flags */
    "Noddy objects",           /* tp_doc */
    0,                         /* tp_traverse */
    0,                         /* tp_clear */
    0,                         /* tp_richcompare */
    0,                         /* tp_weaklistoffset */
    0,                         /* tp_iter */
    0,                         /* tp_iternext */
    Noddy_methods,             /* tp_methods */
    Noddy_members,             /* tp_members */
    Noddy_getseters,           /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    (initproc)Noddy_init,      /* tp_init */
    0,                         /* tp_alloc */
    Noddy_new,                 /* tp_new */
};

static PyMethodDef module_methods[] = {
    {NULL}  /* Sentinel */
};

#ifndef PyMODINIT_FUNC  /* declarations for DLL import/export */
#define PyMODINIT_FUNC void
#endif
PyMODINIT_FUNC
initnoddy3(void)
{
    PyObject* m;

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

    m = Py_InitModule3("noddy3", module_methods,
                       "Example module that creates an extension type.");

    if (m == NULL)
        return;

    Py_INCREF(&NoddyType);
    PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);
}

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

Noddy_getfirst(Noddy *self, void *closure)
{
    Py_INCREF(self->first);
    return self->first;
}

static int
Noddy_setfirst(Noddy *self, PyObject *value, void *closure)
{
  if (value == NULL) {
    PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");
    return -1;
  }

  if (! PyString_Check(value)) {
    PyErr_SetString(PyExc_TypeError,
                    "The first attribute value must be a string");
    return -1;
  }

  Py_DECREF(self->first);
  Py_INCREF(value);
  self->first = value;

  return 0;
}

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

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

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

static PyGetSetDef Noddy_getseters[] = {
    {"first",
     (getter)Noddy_getfirst, (setter)Noddy_setfirst,
     "first name",
     NULL},
    {"last",
     (getter)Noddy_getlast, (setter)Noddy_setlast,
     "last name",
     NULL},
    {NULL}  /* Sentinel */
};

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

Noddy_getseters,           /* tp_getset */

属性ゲッターとセッターを登録します。

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

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

static PyMemberDef Noddy_members[] = {
    {"number", T_INT, offsetof(Noddy, number), 0,
     "noddy number"},
    {NULL}  /* Sentinel */
};

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

static int
Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)
{
    PyObject *first=NULL, *last=NULL, *tmp;

    static char *kwlist[] = {"first", "last", "number", NULL};

    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|SSi", 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()呼び出しに変換できることを意味します。 これらの呼び出しを変更できない唯一の場所は、コンストラクターでこれらのメンバーの初期化が失敗した可能性があるデアロケーターです。

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


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

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

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

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

Noddyの例の2番目のバージョンでは、あらゆる種類のオブジェクトをfirstまたはlast属性 4 に格納できるようにしました。 これは、Noddyオブジェクトがサイクルに参加できることを意味します。

>>> import noddy2
>>> n = noddy2.Noddy()
>>> l = [n]
>>> n.first = l

これはかなりばかげていますが、Noddyの例にサイクリックガベージコレクターのサポートを追加する言い訳になります。 循環ガベージコレクションをサポートするには、タイプは2つのスロットを埋め、これらのスロットを有効にするクラスフラグを設定する必要があります。

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

typedef struct {
    PyObject_HEAD
    PyObject *first;
    PyObject *last;
    int number;
} Noddy;

static int
Noddy_traverse(Noddy *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;
}

static int
Noddy_clear(Noddy *self)
{
    PyObject *tmp;

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

    tmp = self->last;
    self->last = NULL;
    Py_XDECREF(tmp);

    return 0;
}

static void
Noddy_dealloc(Noddy* self)
{
    PyObject_GC_UnTrack(self);
    Noddy_clear(self);
    Py_TYPE(self)->tp_free((PyObject*)self);
}

static PyObject *
Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    Noddy *self;

    self = (Noddy *)type->tp_alloc(type, 0);
    if (self != NULL) {
        self->first = PyString_FromString("");
        if (self->first == NULL) {
            Py_DECREF(self);
            return NULL;
        }

        self->last = PyString_FromString("");
        if (self->last == NULL) {
            Py_DECREF(self);
            return NULL;
        }

        self->number = 0;
    }

    return (PyObject *)self;
}

static int
Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)
{
    PyObject *first=NULL, *last=NULL, *tmp;

    static char *kwlist[] = {"first", "last", "number", NULL};

    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 Noddy_members[] = {
    {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,
     "first name"},
    {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,
     "last name"},
    {"number", T_INT, offsetof(Noddy, number), 0,
     "noddy number"},
    {NULL}  /* Sentinel */
};

static PyObject *
Noddy_name(Noddy* self)
{
    static PyObject *format = NULL;
    PyObject *args, *result;

    if (format == NULL) {
        format = PyString_FromString("%s %s");
        if (format == NULL)
            return NULL;
    }

    if (self->first == NULL) {
        PyErr_SetString(PyExc_AttributeError, "first");
        return NULL;
    }

    if (self->last == NULL) {
        PyErr_SetString(PyExc_AttributeError, "last");
        return NULL;
    }

    args = Py_BuildValue("OO", self->first, self->last);
    if (args == NULL)
        return NULL;

    result = PyString_Format(format, args);
    Py_DECREF(args);

    return result;
}

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

static PyTypeObject NoddyType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "noddy.Noddy",             /* tp_name */
    sizeof(Noddy),             /* tp_basicsize */
    0,                         /* tp_itemsize */
    (destructor)Noddy_dealloc, /* tp_dealloc */
    0,                         /* tp_print */
    0,                         /* tp_getattr */
    0,                         /* tp_setattr */
    0,                         /* tp_compare */
    0,                         /* tp_repr */
    0,                         /* tp_as_number */
    0,                         /* tp_as_sequence */
    0,                         /* tp_as_mapping */
    0,                         /* tp_hash */
    0,                         /* tp_call */
    0,                         /* tp_str */
    0,                         /* tp_getattro */
    0,                         /* tp_setattro */
    0,                         /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT |
        Py_TPFLAGS_BASETYPE |
        Py_TPFLAGS_HAVE_GC,    /* tp_flags */
    "Noddy objects",           /* tp_doc */
    (traverseproc)Noddy_traverse,   /* tp_traverse */
    (inquiry)Noddy_clear,           /* tp_clear */
    0,                         /* tp_richcompare */
    0,                         /* tp_weaklistoffset */
    0,                         /* tp_iter */
    0,                         /* tp_iternext */
    Noddy_methods,             /* tp_methods */
    Noddy_members,             /* tp_members */
    0,                         /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    (initproc)Noddy_init,      /* tp_init */
    0,                         /* tp_alloc */
    Noddy_new,                 /* tp_new */
};

static PyMethodDef module_methods[] = {
    {NULL}  /* Sentinel */
};

#ifndef PyMODINIT_FUNC  /* declarations for DLL import/export */
#define PyMODINIT_FUNC void
#endif
PyMODINIT_FUNC
initnoddy4(void)
{
    PyObject* m;

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

    m = Py_InitModule3("noddy4", module_methods,
                       "Example module that creates an extension type.");

    if (m == NULL)
        return;

    Py_INCREF(&NoddyType);
    PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);
}

トラバーサルメソッドは、サイクルに参加する可能性のあるサブオブジェクトへのアクセスを提供します。

static int
Noddy_traverse(Noddy *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 2.4以降では、訪問関数の呼び出しを自動化する Py_VISIT()マクロが提供されています。 Py_VISIT()を使用すると、Noddy_traverse()を簡略化できます。

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

ノート

tp_traverse 実装では、 Py_VISIT()を使用するために、引数に正確に visit および arg という名前を付ける必要があることに注意してください。 これは、これらの退屈な実装全体の均一性を促進するためです。


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

static int
Noddy_clear(Noddy *self)
{
    PyObject *tmp;

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

    tmp = self->last;
    self->last = NULL;
    Py_XDECREF(tmp);

    return 0;
}

Noddy_clear()で一時変数が使用されていることに注意してください。 一時変数を使用して、参照カウントをデクリメントする前に各メンバーを NULL に設定できるようにします。 これを行うのは、前に説明したように、参照カウントがゼロに低下すると、オブジェクトを呼び出すコードが実行される可能性があるためです。 さらに、ガベージコレクションをサポートするようになったため、ガベージコレクションをトリガーするコードの実行についても心配する必要があります。 ガベージコレクションが実行されると、 tp_traverse ハンドラーが呼び出される可能性があります。 メンバーの参照カウントがゼロになり、その値が NULL に設定されていない場合、Noddy_traverse()が呼び出される可能性はありません。

Python 2.4以降では、参照カウントの注意深いデクリメントを自動化する Py_CLEAR()が提供されています。 Py_CLEAR()を使用すると、Noddy_clear()関数を簡略化できます。

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

Noddy_dealloc()は、__del__メソッドまたはweakrefコールバックを介して任意の関数を呼び出す場合があることに注意してください。 これは、関数内で循環GCをトリガーできることを意味します。 GCは参照カウントがゼロではないと想定しているため、メンバーをクリアする前に PyObject_GC_UnTrack()を呼び出してGCからオブジェクトを追跡解除する必要があります。 これは、 PyObject_GC_UnTrack()Noddy_clear()を使用する再実装されたデアロケーターです。

static void
Noddy_dealloc(Noddy* self)
{
    PyObject_GC_UnTrack(self);
    Noddy_clear(self);
    Py_TYPE(self)->tp_free((PyObject*)self);
}

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

Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /* tp_flags */

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


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

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

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

>>> import shoddy
>>> s = shoddy.Shoddy(range(3))
>>> s.extend(s)
>>> print len(s)
6
>>> print s.increment()
1
>>> print s.increment()
2
#include <Python.h>

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


static PyObject *
Shoddy_increment(Shoddy *self, PyObject *unused)
{
    self->state++;
    return PyInt_FromLong(self->state);
}


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

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


static PyTypeObject ShoddyType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "shoddy.Shoddy",         /* tp_name */
    sizeof(Shoddy),          /* tp_basicsize */
    0,                       /* tp_itemsize */
    0,                       /* tp_dealloc */
    0,                       /* tp_print */
    0,                       /* tp_getattr */
    0,                       /* tp_setattr */
    0,                       /* tp_compare */
    0,                       /* tp_repr */
    0,                       /* tp_as_number */
    0,                       /* tp_as_sequence */
    0,                       /* tp_as_mapping */
    0,                       /* tp_hash */
    0,                       /* tp_call */
    0,                       /* tp_str */
    0,                       /* tp_getattro */
    0,                       /* tp_setattro */
    0,                       /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT |
        Py_TPFLAGS_BASETYPE, /* tp_flags */
    0,                       /* tp_doc */
    0,                       /* tp_traverse */
    0,                       /* tp_clear */
    0,                       /* tp_richcompare */
    0,                       /* tp_weaklistoffset */
    0,                       /* tp_iter */
    0,                       /* tp_iternext */
    Shoddy_methods,          /* tp_methods */
    0,                       /* tp_members */
    0,                       /* tp_getset */
    0,                       /* tp_base */
    0,                       /* tp_dict */
    0,                       /* tp_descr_get */
    0,                       /* tp_descr_set */
    0,                       /* tp_dictoffset */
    (initproc)Shoddy_init,   /* tp_init */
    0,                       /* tp_alloc */
    0,                       /* tp_new */
};

PyMODINIT_FUNC
initshoddy(void)
{
    PyObject *m;

    ShoddyType.tp_base = &PyList_Type;
    if (PyType_Ready(&ShoddyType) < 0)
        return;

    m = Py_InitModule3("shoddy", NULL, "Shoddy module");
    if (m == NULL)
        return;

    Py_INCREF(&ShoddyType);
    PyModule_AddObject(m, "Shoddy", (PyObject *) &ShoddyType);
}

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

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

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

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

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

この型の__init__メソッドでは、基本型の__init__メソッドを呼び出す方法を確認できます。

このパターンは、カスタム new およびdeallocメソッドを使用して型を作成する場合に重要です。 new メソッドは、 tp_alloc を使用してオブジェクトのメモリを実際に作成するべきではありません。これは、 tp_new を呼び出すときに基本クラスによって処理されます。

Shoddyタイプの PyTypeObject()に入力すると、tp_base()用のスロットが表示されます。 クロスプラットフォームコンパイラの問題により、そのフィールドに PyList_Type()を直接入力することはできません。 後でモジュールのinit()関数で実行できます。

PyMODINIT_FUNC
initshoddy(void)
{
    PyObject *m;

    ShoddyType.tp_base = &PyList_Type;
    if (PyType_Ready(&ShoddyType) < 0)
        return;

    m = Py_InitModule3("shoddy", NULL, "Shoddy module");
    if (m == NULL)
        return;

    Py_INCREF(&ShoddyType);
    PyModule_AddObject(m, "Shoddy", (PyObject *) &ShoddyType);
}

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

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


2.2。 タイプメソッド

このセクションの目的は、実装できるさまざまなタイプのメソッドとその機能について簡単に説明することです。

PyTypeObject の定義は次のとおりです。デバッグビルドでのみ使用される一部のフィールドは省略されています:

typedef struct _typeobject {
    PyObject_VAR_HEAD
    char *tp_name; /* For printing, in format "<module>.<name>" */
    int tp_basicsize, tp_itemsize; /* For allocation */

    /* Methods to implement standard operations */

    destructor tp_dealloc;
    printfunc tp_print;
    getattrfunc tp_getattr;
    setattrfunc tp_setattr;
    cmpfunc tp_compare;
    reprfunc tp_repr;

    /* Method suites for standard classes */

    PyNumberMethods *tp_as_number;
    PySequenceMethods *tp_as_sequence;
    PyMappingMethods *tp_as_mapping;

    /* More standard operations (here for binary compatibility) */

    hashfunc tp_hash;
    ternaryfunc tp_call;
    reprfunc tp_str;
    getattrofunc tp_getattro;
    setattrofunc tp_setattro;

    /* Functions to access object as input/output buffer */
    PyBufferProcs *tp_as_buffer;

    /* Flags to define presence of optional/expanded features */
    long tp_flags;

    char *tp_doc; /* Documentation string */

    /* Assigned meaning in release 2.0 */
    /* call function for all accessible objects */
    traverseproc tp_traverse;

    /* delete references to contained objects */
    inquiry tp_clear;

    /* Assigned meaning in release 2.1 */
    /* rich comparisons */
    richcmpfunc tp_richcompare;

    /* weak reference enabler */
    long tp_weaklistoffset;

    /* Added in release 2.2 */
    /* Iterators */
    getiterfunc tp_iter;
    iternextfunc tp_iternext;

    /* Attribute descriptor and subclassing stuff */
    struct PyMethodDef *tp_methods;
    struct PyMemberDef *tp_members;
    struct PyGetSetDef *tp_getset;
    struct _typeobject *tp_base;
    PyObject *tp_dict;
    descrgetfunc tp_descr_get;
    descrsetfunc tp_descr_set;
    long tp_dictoffset;
    initproc tp_init;
    allocfunc tp_alloc;
    newfunc tp_new;
    freefunc tp_free; /* Low-level free-memory routine */
    inquiry tp_is_gc; /* For PyObject_IS_GC */
    PyObject *tp_bases;
    PyObject *tp_mro; /* method resolution order */
    PyObject *tp_cache;
    PyObject *tp_subclasses;
    PyObject *tp_weaklist;

} PyTypeObject;

これが lot のメソッドです。 ただし、あまり心配する必要はありません。定義したい型がある場合は、これらのほんの一握りしか実装しない可能性が非常に高くなります。

ご想像のとおり、これについては、さまざまなハンドラーについて詳しく説明します。 フィールドの順序に影響を与える歴史的な手荷物がたくさんあるため、構造で定義されている順序では進みません。 タイプの初期化により、フィールドが正しい順序に保たれるようにしてください。 多くの場合、必要なすべてのフィールドを含む例を見つけて(0に初期化されている場合でも)、新しいタイプに合わせて値を変更するのが最も簡単です。

char *tp_name; /* For printing */

タイプの名前-前のセクションで述べたように、これはさまざまな場所に表示され、ほぼ完全に診断目的で表示されます。 そのような状況で役立つものを選択してみてください!

int tp_basicsize, tp_itemsize; /* For allocation */

これらのフィールドは、このタイプの新しいオブジェクトが作成されたときに割り当てるメモリの量をランタイムに通知します。 Pythonには、 tp_itemsize フィールドの出番である可変長構造(文字列、リストなど)のサポートが組み込まれています。 これは後で処理されます。

char *tp_doc;

ここに、Pythonスクリプトがobj.__doc__を参照してドキュメント文字列を取得するときに返される文字列(またはそのアドレス)を配置できます。

ここで、基本的な型メソッド、つまりほとんどの拡張型が実装するメソッドについて説明します。

2.2.1。 ファイナライズと割り当て解除

destructor tp_dealloc;

この関数は、型のインスタンスの参照カウントがゼロに減少し、Pythonインタープリターがそれを再利用したいときに呼び出されます。 タイプに解放するメモリまたは実行するその他のクリーンアップがある場合は、ここに配置できます。 オブジェクト自体もここで解放する必要があります。 この関数の例を次に示します。

static void
newdatatype_dealloc(newdatatypeobject * obj)
{
    free(obj->obj_UnderlyingDatatypePtr);
    Py_TYPE(obj)->tp_free(obj);
}

デアロケーター関数の重要な要件の1つは、保留中の例外をそのままにしておくことです。 インタプリタがPythonスタックを巻き戻すときに、デアロケータが頻繁に呼び出されるため、これは重要です。 (通常のリターンではなく)例外が原因でスタックが巻き戻された場合、例外がすでに設定されていることをデロケーターが認識しないように保護するために何も行われません。 追加のPythonコードが実行される可能性のある、デアロケーターが実行するアクションは、例外が設定されたことを検出する場合があります。 これは、インタプリタからの誤解を招くエラーにつながる可能性があります。 これを防ぐ適切な方法は、安全でないアクションを実行する前に保留中の例外を保存し、完了したらそれを復元することです。 これは、 PyErr_Fetch()および PyErr_Restore()関数を使用して実行できます。

static void
my_dealloc(PyObject *obj)
{
    MyObject *self = (MyObject *) obj;
    PyObject *cbresult;

    if (self->my_callback != NULL) {
        PyObject *err_type, *err_value, *err_traceback;
        int have_error = PyErr_Occurred() ? 1 : 0;

        if (have_error)
            PyErr_Fetch(&err_type, &err_value, &err_traceback);

        cbresult = PyObject_CallObject(self->my_callback, NULL);
        if (cbresult == NULL)
            PyErr_WriteUnraisable(self->my_callback);
        else
            Py_DECREF(cbresult);

        if (have_error)
            PyErr_Restore(err_type, err_value, err_traceback);

        Py_DECREF(self->my_callback);
    }
    Py_TYPE(obj)->tp_free((PyObject*)self);
}

2.2.2。 オブジェクトのプレゼンテーション

Pythonでは、オブジェクトのテキスト表現を生成する3つの方法があります。 repr()関数(または同等のバックティック構文)、 str()関数、および print ステートメント。 ほとんどのオブジェクトでは、 print ステートメントは str()関数と同等ですが、必要に応じてFILE*への特殊な印刷が可能です。 これは、効率が問題として識別され、ファイルに書き込む一時的な文字列オブジェクトの作成にコストがかかりすぎることがプロファイリングによって示唆されている場合にのみ実行する必要があります。

これらのハンドラーはすべてオプションであり、ほとんどのタイプはせいぜい tp_str および tp_repr ハンドラーを実装する必要があります。

reprfunc tp_repr;
reprfunc tp_str;
printfunc tp_print;

tp_repr ハンドラーは、呼び出されたインスタンスの表現を含む文字列オブジェクトを返す必要があります。 簡単な例を次に示します。

static PyObject *
newdatatype_repr(newdatatypeobject * obj)
{
    return PyString_FromFormat("Repr-ified_newdatatype{{size:\%d}}",
                               obj->obj_UnderlyingDatatypePtr->size);
}

tp_repr ハンドラーが指定されていない場合、インタープリターは、型の tp_name とオブジェクトの一意の識別値を使用する表現を提供します。

tp_str ハンドラーは str()に対して、上記の tp_repr ハンドラーは repr()に対してです。 つまり、Pythonコードがオブジェクトのインスタンスで str()を呼び出すときに呼び出されます。 その実装は tp_repr 関数と非常に似ていますが、結果の文字列は人間が消費することを目的としています。 tp_str が指定されていない場合は、代わりに tp_repr ハンドラーが使用されます。

簡単な例を次に示します。

static PyObject *
newdatatype_str(newdatatypeobject * obj)
{
    return PyString_FromFormat("Stringified_newdatatype{{size:\%d}}",
                               obj->obj_UnderlyingDatatypePtr->size);
}

Pythonが型のインスタンスを「印刷」する必要があるときはいつでも、print関数が呼び出されます。 たとえば、「node」がタイプTreeNodeのインスタンスである場合、Pythonコードが呼び出すときにprint関数が呼び出されます。

print node

flags引数と1つのフラグPy_PRINT_RAWがあり、文字列引用符なしで、場合によってはエスケープシーケンスを解釈せずに印刷することをお勧めします。

print関数は、引数としてファイルオブジェクトを受け取ります。 そのファイルオブジェクトに書き込みたいと思うでしょう。

印刷機能の例を次に示します。

static int
newdatatype_print(newdatatypeobject *obj, FILE *fp, int flags)
{
    if (flags & Py_PRINT_RAW) {
        fprintf(fp, "<{newdatatype object--size: %d}>",
                obj->obj_UnderlyingDatatypePtr->size);
    }
    else {
        fprintf(fp, "\"<{newdatatype object--size: %d}>\"",
                obj->obj_UnderlyingDatatypePtr->size);
    }
    return 0;
}

2.2.3。 属性管理

属性をサポートできるすべてのオブジェクトについて、対応するタイプは、属性の解決方法を制御する関数を提供する必要があります。 属性を取得できる関数(定義されている場合)と、属性を設定する関数(属性の設定が許可されている場合)が必要です。 属性の削除は特殊なケースであり、ハンドラーに渡される新しい値は NULL です。

Pythonは、2組の属性ハンドラーをサポートしています。 属性をサポートするタイプは、1つのペアの関数を実装するだけで済みます。 違いは、一方のペアが属性の名前をchar*として受け取り、もう一方のペアがPyObject*を受け入れることです。 各タイプは、実装の便宜のためにより意味のあるペアを使用できます。

getattrfunc  tp_getattr;        /* char * version */
setattrfunc  tp_setattr;
/* ... */
getattrofunc tp_getattrofunc;   /* PyObject * version */
setattrofunc tp_setattrofunc;

オブジェクトの属性へのアクセスが常に単純な操作である場合(これについては後で説明します)、属性管理関数のPyObject*バージョンを提供するために使用できる一般的な実装があります。 Python 2.2以降、タイプ固有の属性ハンドラーの実際の必要性はほぼ完全になくなりましたが、利用可能な新しい汎用メカニズムの一部を使用するように更新されていない例が多数あります。

2.2.3.1。 ジェネリック属性管理

バージョン2.2の新機能。


ほとんどの拡張タイプは、 simple 属性のみを使用します。 では、何が属性を単純にするのでしょうか? 満たす必要のある条件は2つだけです。

  1. PyType_Ready()を呼び出すときは、属性の名前を知っている必要があります。
  2. 属性が検索または設定されたことを記録するために特別な処理は必要ありません。また、値に基づいてアクションを実行する必要もありません。

このリストでは、属性の値、値の計算時期、または関連データの保存方法に制限がないことに注意してください。

PyType_Ready()が呼び出されると、型オブジェクトによって参照される3つのテーブルを使用して、型オブジェクトのディクショナリに配置される記述子が作成されます。 各記述子は、インスタンスオブジェクトの1つの属性へのアクセスを制御します。 各テーブルはオプションです。 3つすべてが NULL の場合、型のインスタンスは基本型から継承された属性のみを持ち、 tp_getattro および tp_setattro フィールドを残す必要があります。 ] NULL も同様に、基本型が属性を処理できるようにします。

テーブルは、タイプオブジェクトの3つのフィールドとして宣言されています。

struct PyMethodDef *tp_methods;
struct PyMemberDef *tp_members;
struct PyGetSetDef *tp_getset;

tp_methodsNULL でない場合は、 PyMethodDef 構造体の配列を参照する必要があります。 テーブルの各エントリは、この構造のインスタンスです。

typedef struct PyMethodDef {
    const char  *ml_name;       /* method name */
    PyCFunction  ml_meth;       /* implementation function */
    int          ml_flags;      /* flags */
    const char  *ml_doc;        /* docstring */
} PyMethodDef;

タイプによって提供されるメソッドごとに1つのエントリを定義する必要があります。 基本型から継承されたメソッドにはエントリは必要ありません。 最後に1つの追加エントリが必要です。 これは、配列の終わりを示す番兵です。 番兵のml_nameフィールドは NULL である必要があります。

XXX次のセクションで共有される、構造フィールドの統一された議論を参照する必要があります。

2番目のテーブルは、インスタンスに格納されているデータに直接マップする属性を定義するために使用されます。 さまざまなプリミティブCタイプがサポートされており、アクセスは読み取り専用または読み取り/書き込みの場合があります。 表の構造は次のように定義されています。

typedef struct PyMemberDef {
    char *name;
    int   type;
    int   offset;
    int   flags;
    char *doc;
} PyMemberDef;

テーブルのエントリごとに、記述子が作成され、インスタンス構造から値を抽出できるタイプに追加されます。 type フィールドには、structmember.hヘッダーで定義されているタイプコードの1つが含まれている必要があります。 この値は、Python値をC値に変換する方法とC値から変換する方法を決定するために使用されます。 flagsフィールドは、属性へのアクセス方法を制御するフラグを格納するために使用されます。

XXXこれの一部を共有セクションに移動する必要があります!

structmember.hでは次のフラグ定数が定義されています。 それらはビットごとのORを使用して組み合わせることができます。

絶え間ない 意味
READONLY 決して書き込み可能ではありません。
RO READONLYの省略形。
READ_RESTRICTED 制限付きモードでは読み取りできません。
WRITE_RESTRICTED 制限付きモードでは書き込みできません。
RESTRICTED 制限付きモードでは読み取りまたは書き込みができません。

tp_members テーブルを使用して実行時に使用される記述子を作成することの興味深い利点は、この方法で定義された属性は、テーブルにテキストを提供するだけで、関連付けられたドキュメント文字列を持つことができることです。 アプリケーションは、イントロスペクションAPIを使用してクラスオブジェクトから記述子を取得し、__doc__属性を使用してドキュメント文字列を取得できます。

tp_methods テーブルと同様に、name値が NULL のセンチネルエントリが必要です。


2.2.3.2。 タイプ固有の属性管理

簡単にするために、ここではchar*バージョンのみを示します。 nameパラメータのタイプは、インターフェイスのchar*フレーバーとPyObject*フレーバーの唯一の違いです。 この例は、上記の一般的な例と効果的に同じことを行いますが、Python2.2で追加された一般的なサポートを使用していません。 これを示すことの価値は2つあります。古いバージョンのPythonに移植可能な方法で基本的な属性管理を行う方法を示し、ハンドラー関数が呼び出される方法を説明します。そのため、必要に応じて機能を拡張する必要があります。 、何をする必要があるかを理解できます。

tp_getattr ハンドラーは、オブジェクトが属性ルックアップを必要とするときに呼び出されます。 これは、クラスの__getattr__()メソッドが呼び出されるのと同じ状況で呼び出されます。

これを処理する可能性のある方法は、(1)一連の関数(以下の例のnewdatatype_getSize()newdatatype_setSize()など)を実装すること、(2)これらの関数をリストするメソッドテーブルを提供すること、および(3)そのテーブルでのルックアップの結果を返すgetattr関数を提供します。 メソッドテーブルは、タイプオブジェクトの tp_methods フィールドと同じ構造を使用します。

次に例を示します。

static PyMethodDef newdatatype_methods[] = {
    {"getSize", (PyCFunction)newdatatype_getSize, METH_VARARGS,
     "Return the current size."},
    {"setSize", (PyCFunction)newdatatype_setSize, METH_VARARGS,
     "Set the size."},
    {NULL, NULL, 0, NULL}           /* sentinel */
};

static PyObject *
newdatatype_getattr(newdatatypeobject *obj, char *name)
{
    return Py_FindMethod(newdatatype_methods, (PyObject *)obj, name);
}

tp_setattr ハンドラーは、クラスインスタンスの__setattr__()または__delattr__()メソッドが呼び出されるときに呼び出されます。 属性を削除する必要がある場合、3番目のパラメーターは NULL になります。 これは単に例外を発生させる例です。 これが本当に必要なすべてである場合は、 tp_setattr ハンドラーを NULL に設定する必要があります。

static int
newdatatype_setattr(newdatatypeobject *obj, char *name, PyObject *v)
{
    (void)PyErr_Format(PyExc_RuntimeError, "Read-only attribute: \%s", name);
    return -1;
}

2.2.4。 オブジェクトの比較

cmpfunc tp_compare;

tp_compare ハンドラーは、比較が必要であり、オブジェクトが要求された比較に一致する特定のリッチ比較メソッドを実装していない場合に呼び出されます。 (定義され、 PyObject_Compare()または PyObject_Cmp()関数が使用されている場合、または cmp()がPythonから使用されている場合は、常に使用されます。) __cmp__()メソッドに類似しています。 この関数は、 obj1obj2 より小さい場合は-1を返し、等しい場合は0を返し、の場合は1を返す必要があります。 ] obj1obj2 より大きい。 (以前は、それぞれより小さいおよびより大きい任意の負または正の整数を返すことが許可されていました。Python2.2以降、これは許可されなくなりました。 将来的には、他の戻り値に別の意味が割り当てられる可能性があります。)

tp_compare ハンドラーで例外が発生する場合があります。 この場合、負の値を返す必要があります。 呼び出し元は、 PyErr_Occurred()を使用して例外をテストする必要があります。

実装例は次のとおりです。

static int
newdatatype_compare(newdatatypeobject * obj1, newdatatypeobject * obj2)
{
    long result;

    if (obj1->obj_UnderlyingDatatypePtr->size <
        obj2->obj_UnderlyingDatatypePtr->size) {
        result = -1;
    }
    else if (obj1->obj_UnderlyingDatatypePtr->size >
             obj2->obj_UnderlyingDatatypePtr->size) {
        result = 1;
    }
    else {
        result = 0;
    }
    return result;
}

2.2.5。 抽象プロトコルのサポート

Pythonは、さまざまな abstract 'プロトコル;'をサポートしています。 これらのインターフェースを使用するために提供される特定のインターフェースは、 Abstract Objects Layer に記載されています。

これらの抽象的なインターフェースの多くは、Python実装の開発の初期に定義されました。 特に、数、マッピング、およびシーケンスプロトコルは、当初からPythonの一部でした。 他のプロトコルは時間の経過とともに追加されてきました。 型の実装からのいくつかのハンドラールーチンに依存するプロトコルの場合、古いプロトコルは、型オブジェクトによって参照されるハンドラーのオプションのブロックとして定義されています。 新しいプロトコルの場合、メインタイプオブジェクトに追加のスロットがあり、スロットが存在することを示すフラグビットが設定されており、インタプリタがチェックする必要があります。 (フラグビットは、スロット値が非 NULL であることを示していません。 フラグはスロットの存在を示すために設定できますが、スロットはまだ埋められていない可能性があります。)

PyNumberMethods   *tp_as_number;
PySequenceMethods *tp_as_sequence;
PyMappingMethods  *tp_as_mapping;

オブジェクトを数値、シーケンス、またはマッピングオブジェクトのように機能させたい場合は、Cタイプ PyNumberMethodsPySequenceMethods を実装する構造体のアドレスを配置します。 、または PyMappingMethods 。 この構造に適切な値を入力するのはあなた次第です。 これらのそれぞれの使用例は、PythonソースディストリビューションのObjectsディレクトリにあります。

hashfunc tp_hash;

この関数を提供することを選択した場合、データ型のインスタンスのハッシュ番号を返す必要があります。 これは適度に無意味な例です:

static long
newdatatype_hash(newdatatypeobject *obj)
{
    long result;
    result = obj->obj_UnderlyingDatatypePtr->size;
    result = result * 3;
    return result;
}
ternaryfunc tp_call;

この関数は、データ型のインスタンスが「呼び出された」ときに呼び出されます。たとえば、obj1がデータ型のインスタンスであり、Pythonスクリプトにobj1('hello')が含まれている場合、 tp_call ハンドラーが呼び出されます。

この関数は3つの引数を取ります。

  1. arg1 は、呼び出しの対象となるデータ型のインスタンスです。 呼び出しがobj1('hello')の場合、 arg1obj1です。
  2. arg2 は、呼び出しへの引数を含むタプルです。 PyArg_ParseTuple()を使用して引数を抽出できます。
  3. arg3 は、渡されたキーワード引数の辞書です。 これが NULL 以外で、キーワード引数をサポートしている場合は、 PyArg_ParseTupleAndKeywords()を使用して引数を抽出します。 キーワード引数をサポートしたくなく、これが NULL 以外の場合は、TypeErrorを発生させて、キーワード引数がサポートされていないことを示すメッセージを表示します。

これは、呼び出し関数の実装の卑劣な例です。

/* Implement the call function.
 *    obj1 is the instance receiving the call.
 *    obj2 is a tuple containing the arguments to the call, in this
 *         case 3 strings.
 */
static PyObject *
newdatatype_call(newdatatypeobject *obj, PyObject *args, PyObject *other)
{
    PyObject *result;
    char *arg1;
    char *arg2;
    char *arg3;

    if (!PyArg_ParseTuple(args, "sss:call", &arg1, &arg2, &arg3)) {
        return NULL;
    }
    result = PyString_FromFormat(
        "Returning -- value: [\%d] arg1: [\%s] arg2: [\%s] arg3: [\%s]\n",
        obj->obj_UnderlyingDatatypePtr->size,
        arg1, arg2, arg3);
    printf("\%s", PyString_AS_STRING(result));
    return result;
}

XXXここにいくつかのフィールドを追加する必要があります…

/* Added in release 2.2 */
/* Iterators */
getiterfunc tp_iter;
iternextfunc tp_iternext;

これらの関数は、イテレータプロトコルのサポートを提供します。 コンテンツ(反復中に生成される可能性がある)の反復をサポートするオブジェクトは、tp_iterハンドラーを実装する必要があります。 tp_iterハンドラーによって返されるオブジェクトは、tp_iterハンドラーとtp_iternextハンドラーの両方を実装する必要があります。 両方のハンドラーは、呼び出されているインスタンスという1つのパラメーターを受け取り、新しい参照を返します。 エラーの場合は、例外を設定して NULL を返す必要があります。

反復可能なコレクションを表すオブジェクトの場合、tp_iterハンドラーはイテレーターオブジェクトを返す必要があります。 イテレータオブジェクトは、反復の状態を維持する役割を果たします。 (リストやタプルのように)互いに干渉しない複数のイテレーターをサポートできるコレクションの場合は、新しいイテレーターを作成して返す必要があります。 (通常は反復の副作用のために)1回だけ反復できるオブジェクトは、自身への新しい参照を返すことによってこのハンドラーを実装する必要があり、tp_iternextハンドラーも実装する必要があります。 ファイルオブジェクトは、そのようなイテレータの例です。

イテレータオブジェクトは両方のハンドラを実装する必要があります。 tp_iterハンドラーは、イテレーターへの新しい参照を返す必要があります(これは、破壊的にのみ反復できるオブジェクトのtp_iterハンドラーと同じです)。 tp_iternextハンドラーは、反復内の次のオブジェクトがある場合は、そのオブジェクトへの新しい参照を返す必要があります。 反復が終了に達した場合、例外を設定せずに NULL を返すか、StopIterationを設定する場合があります。 例外を回避すると、パフォーマンスがわずかに向上する可能性があります。 実際のエラーが発生した場合は、例外を設定して NULL を返す必要があります。


2.2.6。 弱参照サポート

Pythonの弱参照実装の目標の1つは、弱参照の恩恵を受けないオブジェクト(数値など)にオーバーヘッドを発生させることなく、任意の型が弱参照メカニズムに参加できるようにすることです。

オブジェクトを弱参照可能にするには、拡張で弱参照メカニズムを使用するためにインスタンス構造にPyObject*フィールドを含める必要があります。 オブジェクトのコンストラクターによって NULL に初期化する必要があります。 また、対応するタイプオブジェクトの tp_weaklistoffset フィールドをフィールドのオフセットに設定する必要があります。 たとえば、インスタンスタイプは次の構造で定義されます。

typedef struct {
    PyObject_HEAD
    PyClassObject *in_class;       /* The class object */
    PyObject      *in_dict;        /* A dictionary */
    PyObject      *in_weakreflist; /* List of weak references */
} PyInstanceObject;

インスタンスの静的に宣言された型オブジェクトは、次のように定義されます。

PyTypeObject PyInstance_Type = {
    PyObject_HEAD_INIT(&PyType_Type)
    0,
    "module.instance",

    /* Lots of stuff omitted for brevity... */

    Py_TPFLAGS_DEFAULT,                         /* tp_flags */
    0,                                          /* tp_doc */
    0,                                          /* tp_traverse */
    0,                                          /* tp_clear */
    0,                                          /* tp_richcompare */
    offsetof(PyInstanceObject, in_weakreflist), /* tp_weaklistoffset */
};

型コンストラクターは、弱参照リストを NULL に初期化する役割を果たします。

static PyObject *
instance_new() {
    /* Other initialization stuff omitted for brevity */

    self->in_weakreflist = NULL;

    return (PyObject *) self;
}

さらに唯一の追加は、デストラクタが弱参照マネージャーを呼び出して弱参照をクリアする必要があることです。 これは、弱参照リストが NULL 以外の場合にのみ必要です。

static void
instance_dealloc(PyInstanceObject *inst)
{
    /* Allocate temporaries if needed, but do not begin
       destruction just yet.
     */

    if (inst->in_weakreflist != NULL)
        PyObject_ClearWeakRefs((PyObject *) inst);

    /* Proceed with object destruction normally. */
}

2.2.7。 その他の提案

これらの関数のほとんどは省略できることに注意してください。その場合、値として0を指定します。 提供する必要のある関数ごとにタイプ定義があります。 これらは、Pythonのソースディストリビューションに付属するPythonインクルードディレクトリのobject.hにあります。

新しいデータ型に特定のメソッドを実装する方法を学習するには、次の手順を実行します。Pythonソースディストリビューションをダウンロードして解凍します。 Objectsディレクトリに移動し、Cソースファイルでtp_と必要な機能(たとえば、tp_printまたはtp_compare)を検索します。 実装したい関数の例があります。

オブジェクトが実装しているタイプのインスタンスであることを確認する必要がある場合は、 PyObject_TypeCheck()関数を使用してください。 その使用例は次のようになります。

if (! PyObject_TypeCheck(some_object, &MyType)) {
    PyErr_SetString(PyExc_TypeError, "arg #1 not a mything");
    return NULL;
}

脚注

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