2017-03-01 11 views
1
class foo: 
    def __init__(self, data): 
     self.data = data 
    def __len__(self): 
     return self.data 

dataの文字列を渡して実行すると、このクラスのインスタンスでlenを呼び出すときにエラーが発生します。具体的には'str' object cannot be interpreted as an integerです。lenが呼び出されると、Pythonはどのようにして__len__の戻り値が整数であることを保証しますか?

__len__returnステートメントは整数でなければなりませんか?私はそれを上書きしていれば、私が望むものを出力することができるはずだと思うので、なぜこれは不可能なのでしょうか?

+3

あなたは '__len__'からあなたが望むものを返すことができますが、それは' len'がそれを受け入れることを意味するものではありません。 'len'は' def len(x):return x .__ len __() 'だけではありません。 – user2357112

+0

長さを文字列にするのは理にかなっていますか?長さは整数です... – Li357

+2

[docs](https://docs.python.org/3/reference/datamodel.html#object.__len__)によると、関数**は整数を返します> = 0これは、あなたがそれをしないと、動作することが保証されないことを意味します。 – Archimaredes

答えて

4

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を呼び出します。


のはさておき、迅速を見てみましょう:(などlistset、など)組み込み型のために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つのこと:

    負の数が返された場合
  1. ValueErrorは、メッセージ"__len__() should return >= 0"を上げています。これはです。私はlen(foo(-1))に電話しようとしたときにエラーが発生しました。
  2. 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__' 
+0

徹底的な対応をありがとう。涼しいシーンの後ろにあるすべてのものがパイソンを動作させるのを見るために。 –

関連する問題