1

doctestを使ってユニットテストを行う次の機能があります。

from collections import deque

def fill_q(histq=deque([])):
    """
    >>> fill_q()
    deque([1, 2, 3])
    >>> fill_q()
    deque([1, 2, 3])
    """
    if histq:
        assert(len(histq) == 0)
    histq.append(1)
    histq.append(2)
    histq.append(3)
    return histq

if __name__ == "__main__":
    import doctest
    doctest.testmod()

最初のケースは成功しますが、fill_qへの2番目の呼び出しは失敗しますが、それでも同じコードです。

**********************************************************************
File "trial.py", line 7, in __main__.fill_q
Failed example:
    fill_q()
Exception raised:
    Traceback (most recent call last):
      File "/usr/lib/python2.7/doctest.py", line 1289, in __run
        compileflags, 1) in test.globs
      File "<doctest __main__.fill_q[1]>", line 1, in <module>
        fill_q()
      File "trial.py", line 11, in fill_q
        assert(len(histq) == 0)
    AssertionError
**********************************************************************
1 items had failures:
   1 of   2 in __main__.fill_q
***Test Failed*** 1 failures.

doctesthistqは最初のテスト呼び出しからローカル変数を再利用しているようですが、なぜこれを行っているのですか?これは非常にばかげた振る舞いです(私がここで狂ったようにやっていないという条件で)。

4

2 に答える 2

1

非常に一般的なPythonの間違いを犯しています-オブジェクトを関数のデフォルトコンストラクターとして設定した場合、その関数の次の呼び出しで再初期化されません-そのオブジェクトへの変更は関数呼び出し間で保持されます。

この問題を回避するためのより良い戦略は、デフォルトを既知の値に設定し、それをチェックすることです。

def fill_q(histq=None):
    if histq is None:
        histq = deque([])
    ...
于 2012-10-03T21:45:28.787 に答える
1

問題は、doctestではなく、で使用しているデフォルトのパラメータにありますdef fill_q(histq=deque([]))。これに似ています:

>>> from collections import deque
>>> 
>>> def fill_q(data=deque([])):
...     data.append(1)
...     return data
... 
>>> fill_q()
deque([1])
>>> fill_q()
deque([1, 1])
>>> fill_q()
deque([1, 1, 1])

この一見奇妙な動作は、リストや辞書などのデフォルト値として可変オブジェクトを使用する場合に発生します。実際には同じオブジェクトを使用しています。

>>> id(fill_q())
4485636624
>>> id(fill_q())
4485636624
>>> id(fill_q())
4485636624

なんで?

defデフォルトのパラメータ値は、それらが属するステートメントが実行されたときにのみ常に評価されます[ ref ]。


この間違いを回避する方法:

None代わりに、または任意のオブジェクトのデフォルトパラメータとして使用します。

my_obj = object()
def sample_func(value=my_obj):
    if value is my_obj:
        value = expression
    # then modify value 

いつ使うの?

  1. グローバル名のローカル再バインド:

    import math
    
    def fast_func(sin=math.sin, cos=math.cos):
    
  2. メモ化に使用できます(たとえば、特定の再帰をより高速に実行します)

于 2012-10-03T21:47:24.993 に答える