読者です 読者をやめる 読者になる 読者になる

ぱいそんのおぶじぇくと-りすとへん(1)「生成」

pyrexを調べていたらPythonソースコードに興味を持ったので、Pythonオブジェクトに関してきちんとソースを読んで書いてみます。
まずはオブジェクトをどうPythonが扱っているか?
興味ない人の方が絶対に多いのでほぼ自身の勉強用です。
あと参考ソースはCPython2.5です。


で、かんたんというか基礎のリストから。
まずは「生成」。
Pythonのlistは所謂一般的な配列の代替として実装されています。
ソースコード上ではPyListObjectというCの構造体がそれです。


中身は

typedef struct {
    PyObject_VAR_HEAD
    PyObject **ob_item;
    Py_ssize_t allocated;
} PyListObject;

PyObject_VAR_HEADは各オブジェクト共通のヘッダです。

コメントで示される不変式としては、

  • 0 <= ob_size <= allocated
  • len(list) == ob_size
  • ob_item == NULL implies ob_size == allocated == 0

こんな感じに成ってます。

  • オブジェクトのサイズは0より大きいか等しく、確保しているメモリ領域はオブジェクトのサイズより大きいか等しい。
  • lenが返すのはオブジェクトの個数であり、確保しているメモリ領域ではない。
  • オブジェクトを何もいれていないのであればオブジェクトの個数も確保しているメモリ領域も0である。


実際listのソースだけで2931行あるのでちょこっと読むのに楽しい量です。


Pythonのlistって生成時に何をしているか気になりませんか?
じゃ、見てみましょう。


実際のCの関数は

PyObject *
PyList_New(Py_ssize_t size)

でlistを生成しています。


サイズを指定して、Pythonオブジェクトのポインタを返すだけの単純な関数です。
ちなみにPythonObjectとPyListObjectは互換性があり、実際に返しているのはPyListObjectのポインタです。


ここで面白いところはfree_listsというものがあるところです。
これはstaticなメモリ領域で最初から「既にGCから取得済みのメモリを指すポインタ配列として」静的なメモリとして確保されています。


最初からメモリ管理領域からnewでとる訳ではないんですね。(キャッシュですね)

#define MAXFREELISTS 80
static PyListObject *free_lists[MAXFREELISTS];

という記述があり、最初から80個分のリスト領域は確保していることになります。
free_listsが余っていればfree_listsが指している領域を取得し、
なければPyObject_GC_NewというPyObjectを生成するための関数でメモリをもらってきます。


指定サイズが0なら0で初期化されますが、
サイズが指定された場合はそのアイテムを参照する領域をPyMem_MALLOCで持ってきます。
サイズはもちろんnbytes = size * sizeof(PyObject *);で参照するオブジェクトのポインタの個数です。
ここにオブジェクトを参照するポインタが入ります。


締めは

op->ob_size = size;
op->allocated = size;

で、サイズと確保領域を覚えてlistのポインタを返して終わりです。
かんたんですね。
生成時のソースを引用します。

PyObject *
PyList_New(Py_ssize_t size)
{
	PyListObject *op;
	size_t nbytes;

	if (size < 0) {
		PyErr_BadInternalCall();
		return NULL;
	}
	nbytes = size * sizeof(PyObject *);
	/* Check for overflow */
	if (nbytes / sizeof(PyObject *) != (size_t)size)
		return PyErr_NoMemory();
	if (num_free_lists) {
		num_free_lists--;
		op = free_lists[num_free_lists];
		_Py_NewReference((PyObject *)op);
	} else {
		op = PyObject_GC_New(PyListObject, &PyList_Type);
		if (op == NULL)
			return NULL;
	}
	if (size <= 0)
		op->ob_item = NULL;
	else {
		op->ob_item = (PyObject **) PyMem_MALLOC(nbytes);
		if (op->ob_item == NULL) {
			Py_DECREF(op);
			return PyErr_NoMemory();
		}
		memset(op->ob_item, 0, nbytes);
	}
	op->ob_size = size;
	op->allocated = size;
	_PyObject_GC_TRACK(op);
	return (PyObject *) op;
}

とても読みやすいソースですね。