2016-07-23 129 views
10

我在某個類上第一次實現了__contains__方法,這種行爲並非我所期望的。我懷疑有一些微妙的問題,我不明白,我希望有人能夠啓發我。Python`in`與`__contains__`的功能

在我看來,in運算符不會簡單地包裝對象的__contains__方法,但它也試圖強制輸出__contains__爲布爾值。例如,考慮類

class Dummy(object): 
    def __contains__(self, val): 
     # Don't perform comparison, just return a list as 
     # an example. 
     return [False, False] 

in運營商和到__contains__方法的返回非常不同的輸出直接調用:

>>> dum = Dummy() 
>>> 7 in dum 
True 
>>> dum.__contains__(7) 
[False, False] 

再次,它看起來像in呼籲__contains__但隨後強迫的結果到bool。除了__contains__documentation表示__contains__應該永遠只返回TrueFalse這一事實以外,我無法在任何地方找到此行爲。

我很高興遵守約定,但有人能告訴我in__contains__之間的確切關係嗎?

尾聲

我決定選擇@禮 - korvigo答案,但每個人都應該看看@阿什維尼 - 喬杜裏comment有關bug,以下。

+0

因爲你的contains方法返回等價於bool([False,False]) – x1Mike7x

+2

相關bug:['in'應該與'__contains__'的返回值一致](https://bugs.python.org/issue16011 ) –

+1

@AshwiniChaudhary:你能寫這個評論作爲答案嗎?只是一行一行就好了。我從來沒有見過這個錯誤報告,它正好回答了我的問題。我並不關心'in'的具體實現,因爲我關心設計推理和明顯缺乏文檔。如果你發佈這個答案,我會選擇你的答案作爲接受的答案。 –

回答

8

使用源碼,盧克!

讓我們跟蹤下來in符實現

>>> import dis 
>>> class test(object): 
...  def __contains__(self, other): 
...   return True 

>>> def in_(): 
...  return 1 in test() 

>>> dis.dis(in_) 
    2   0 LOAD_CONST    1 (1) 
       3 LOAD_GLOBAL    0 (test) 
       6 CALL_FUNCTION   0 (0 positional, 0 keyword pair) 
       9 COMPARE_OP    6 (in) 
       12 RETURN_VALUE 

正如你所看到的,in運算符將成爲COMPARE_OP虛擬機指令。你可以發現,在ceval.c

TARGET(COMPARE_OP) 
    w = POP(); 
    v = TOP(); 
    x = cmp_outcome(oparg, v, w); 
    Py_DECREF(v); 
    Py_DECREF(w); 
    SET_TOP(x); 
    if (x == NULL) break; 
    PREDICT(POP_JUMP_IF_FALSE); 
    PREDICT(POP_JUMP_IF_TRUE); 
    DISPATCH(); 

看看交換機之一cmp_outcome()

case PyCmp_IN: 
    res = PySequence_Contains(w, v); 
    if (res < 0) 
     return NULL; 
    break; 

在這裏,我們有PySequence_Contains呼叫

int 
PySequence_Contains(PyObject *seq, PyObject *ob) 
{ 
    Py_ssize_t result; 
    PySequenceMethods *sqm = seq->ob_type->tp_as_sequence; 
    if (sqm != NULL && sqm->sq_contains != NULL) 
     return (*sqm->sq_contains)(seq, ob); 
    result = _PySequence_IterSearch(seq, ob, PY_ITERSEARCH_CONTAINS); 
    return Py_SAFE_DOWNCAST(result, Py_ssize_t, int); 
} 

總是返回int(一布爾)。

P.S.

感謝Martijn Pieters提供way來找到in運營商的實施。

+0

感謝您的詳細解答,但我一直在尋找更多的設計背後的原因,並且明顯缺乏文檔,而不是執行'in'。無論如何,我都會回答你的回答,因爲它是有用的信息。 –

+0

@ joshua.r.smith我想,在這種情況下,實現與推理直接相關。基本上,這就是Python-C API的構想。至於缺乏文檔,文檔並沒有真正引用「True」或「False」,他們只是說'__cointains__'應該返回true或者false(即可以評估爲「True」或「False」 )。你可以在整個文檔中看到,他們在哪裏顯式使用'True'和'False'。無論如何,他們可能已經寫得不那麼含糊不清,所以你可以提交文檔補丁報告。 –

5

Python reference for __contains__它寫道,__contains__應返回TrueFalse

如果返回值不是布爾值,它將轉換爲布爾值。這裏是證明:

class MyValue: 
    def __bool__(self): 
     print("__bool__ function runned") 
     return True 

class Dummy: 
    def __contains__(self, val): 
     return MyValue() 

現在寫在外殼:

>>> dum = Dummy() 
>>> 7 in dum 
__bool__ function runned 
True 

而且bool()非空列表返回True

編輯:

這只是對__contains__文件,如果你真的想看到確切的關係,你應該考慮尋找到的源代碼,雖然我不知道到底在哪,但它已經回答了。在documentation for comparison這是寫:

然而,這些方法可以返回任何值,所以如果比較運算符在布爾上下文(例如,在if語句的條件)中使用,Python會調用bool()的價值以確定結果是真是假。

所以你可以猜到它與__contains__相似。