TL; Cレベルで
DRは、Pythonは__len__
へのコールの出力をキャッチし、それが正しいことを確認し、その上にいくつかの検証を行い、特殊なスロットに__len__
を挿入します。これに答えるためには
、我々はlen
はPythonで呼び出されたボンネットの下に何が起こるかのウサギの穴の下ビットを行かなければなりません。
まず、いくつかの動作を確立しましょう。
>>> class foo:
... def __init__(self, data):
... self.data = data
... def __len__(self):
... return self.data
...
>>> len(foo(-1))
Traceback:
...
ValueError: __len__() should return >= 0
>>> len(foo('5'))
Traceback:
...
TypeError: 'str' object cannot be interpreted as an integer
>>> len(foo(5))
5
あなたがlen
を呼び出すと、Cの関数builtin_len
が呼び出されます。これを見てみましょう。
static PyObject *
builtin_len(PyObject *module, PyObject *obj)
/*[clinic end generated code: output=fa7a270d314dfb6c input=bc55598da9e9c9b5]*/
{
Py_ssize_t res;
res = PyObject_Size(obj); // <=== THIS IS WHAT IS IMPORTANT!!!
if (res < 0 && PyErr_Occurred())
return NULL;
return PyLong_FromSsize_t(res);
}
あなたはPyObject_Size
関数が呼び出されていることがわかります - この関数は任意のPythonオブジェクトのサイズを返します。ウサギの穴をさらに下に移動しましょう。
Py_ssize_t
PyObject_Size(PyObject *o)
{
PySequenceMethods *m;
if (o == NULL) {
null_error();
return -1;
}
m = o->ob_type->tp_as_sequence;
if (m && m->sq_length)
return m->sq_length(o); // <==== THIS IS WHAT IS IMPORTANT!!!
return PyMapping_Size(o);
}
タイプsq_length
関数(系列長)を規定する場合は、チェックし、もしそうであれば、長さを得るためにそれを呼び出します。 Cレベルでは、Pythonは、__len__
を定義するすべてのオブジェクトをシーケンスまたはマッピングとして分類しているようです(たとえPythonレベルでそれらを考える方法ではないとしても)。私たちの場合、Pythonはこのクラスをシーケンスと考えるので、sq_length
を呼び出します。
のはさておき、迅速を見てみましょう:(などlist
、set
、など)組み込み型のためにPythonは、実際の長さを計算する関数を呼び出しますが、これを行う、Cの構造体に格納されている値にアクセスしていませんとても早い。これらの組み込み型のそれぞれは、アクセサメソッドをsq_length
に割り当てることによってこれにアクセスする方法を定義します。さんがhow this is implemented for listsを簡単に覗いてみましょう:
static Py_ssize_t
list_length(PyListObject *a)
{
return Py_SIZE(a); // <== THIS IS A MACRO for (PyVarObject*) a->ob_size;
}
static PySequenceMethods list_as_sequence = {
...
(lenfunc)list_length, /* sq_length */
...
};
ob_size
ストアオブジェクトのサイズ(リストの要素、すなわち数)。したがって、sq_length
が呼び出されると、それはob_size
の値を得るためにlist_length
関数に送られます。
OK、それはそれは、組み込みタイプのために行われている方法ですので...それはどのように私たちのfoo
などのカスタムクラスのために働くのでしょうか? 「dunderメソッド」(__len__
など)は特別なので、Pythonはクラスでそれらを検出し、それらを特別に扱います(特別なスロットに挿入する)。
これのほとんどはtypeobject.cで処理されます。 __len__
機能が傍受され、sq_length
スロットに割り当てられます(内蔵のように)near the bottom of the file。
SQSLOT("__len__", sq_length, slot_sq_length, wrap_lenfunc,
"__len__($self, /)\n--\n\nReturn len(self)."),
slot_sq_length
機能は、私たちが最終的にあなたの質問に答えることができる場所です。ここでは、ノートの
static Py_ssize_t
slot_sq_length(PyObject *self)
{
PyObject *res = call_method(self, &PyId___len__, NULL);
Py_ssize_t len;
if (res == NULL)
return -1;
len = PyNumber_AsSsize_t(res, PyExc_OverflowError); // <=== HERE!!!
Py_DECREF(res);
if (len < 0) { // <== AND HERE!!!
if (!PyErr_Occurred())
PyErr_SetString(PyExc_ValueError,
"__len__() should return >= 0");
return -1;
}
return len;
}
2つのこと:
負の数が返された場合
- 、
ValueError
は、メッセージ"__len__() should return >= 0"
を上げています。これはです。私はlen(foo(-1))
に電話しようとしたときにエラーが発生しました。
- Pythonは(
Py_ssize_t
がが容器内のインデックスのものにできることが保証されている整数の特殊なタイプのようなものであるsize_t
のバージョンは、署名された)を返す前Py_ssize_t
に__len__
の戻り値を強制しようとします。
これで、PyNumber_AsSsize_t
の実装を見てみましょう。ちょっと長いので、関連性のないものは省略します。
Py_ssize_t
PyNumber_AsSsize_t(PyObject *item, PyObject *err)
{
Py_ssize_t result;
PyObject *runerr;
PyObject *value = PyNumber_Index(item);
if (value == NULL)
return -1;
/* OMITTED FOR BREVITY */
ここでは関係ビットは、Pythonは、インデックス付けに適した整数に任意のオブジェクトを変換するために使用する、PyNumber_Index
です。 ここにあなたの質問に対する実際の答えがあります。私は少し注釈を付けました。
PyObject *
PyNumber_Index(PyObject *item)
{
PyObject *result = NULL;
if (item == NULL) {
return null_error();
}
if (PyLong_Check(item)) { // IS THE OBJECT ALREADY AN int? IF SO, RETURN IT NOW.
Py_INCREF(item);
return item;
}
if (!PyIndex_Check(item)) { // DOES THE OBJECT DEFINE __index__? IF NOT, FAIL.
PyErr_Format(PyExc_TypeError,
"'%.200s' object cannot be interpreted "
"as an integer", item->ob_type->tp_name);
return NULL;
}
result = item->ob_type->tp_as_number->nb_index(item);
if (!result || PyLong_CheckExact(result))
return result;
if (!PyLong_Check(result)) { // IF __index__ DOES NOT RETURN AN int, FAIL.
PyErr_Format(PyExc_TypeError,
"__index__ returned non-int (type %.200s)",
result->ob_type->tp_name);
Py_DECREF(result);
return NULL;
}
/* Issue #17576: warn if 'result' not of exact type int. */
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
"__index__ returned non-int (type %.200s). "
"The ability to return an instance of a strict subclass of int "
"is deprecated, and may be removed in a future version of Python.",
result->ob_type->tp_name)) {
Py_DECREF(result);
return NULL;
}
return result;
}
表示されたエラーに基づいて、我々は'5'
は__index__
を定義していないことがわかります。
>>> '5'.__index__()
Traceback:
...
AttributeError: 'str' object has no attribute '__index__'
あなたは '__len__'からあなたが望むものを返すことができますが、それは' len'がそれを受け入れることを意味するものではありません。 'len'は' def len(x):return x .__ len __() 'だけではありません。 – user2357112
長さを文字列にするのは理にかなっていますか?長さは整数です... – Li357
[docs](https://docs.python.org/3/reference/datamodel.html#object.__len__)によると、関数**は整数を返します> = 0これは、あなたがそれをしないと、動作することが保証されないことを意味します。 – Archimaredes