mutable関数の引数のデフォルト値をうまく使用していますか?

mutableオブジェクトを関数の引数のデフォルト値として設定するのはよくある間違いです。以下は、 David Goodger

>>> def bad_append(new_item, a_list=[]):
        a_list.append(new_item)
        return a_list
>>> print bad_append('one')
['one']
>>> print bad_append('two')
['one', 'two']

これが起こる理由は、ここ

そして、今私の質問のために:この構文の良いユースケースはありますか?

つまり、遭遇するすべての人が同じミスを犯し、デバッグし、その問題を理解し、その問題を回避しようとした場合、そのような構文にはどのような用途がありますか?

38
私はこれも疑問に思っています。この例はWeb上にありますが、意味をなさない - 渡されたリストを変更したいときやデフォルトが意味をなさない場合、または新しいリストを返す場合は、すぐにコピーを作成する必要があります機能に入ると私は両方をすることが有用な場合を想像することはできません。
追加された 著者 Mark Ransom,
私はちょうど私が上で不平を言う問題を持たないより現実的な例を見つけました。デフォルトはクラスの __ init __ 関数の引数で、インスタンス変数に設定されます。これは完全に有効なものですが、変更可能なデフォルトではすべてがひどく間違っています。 stackoverflow.com/questions/43768055/…
追加された 著者 Mark Ransom,
@EricDuminilこれはPythonのバグではなく、ほとんどの人が受け入れるよりも、内部をより深く理解する必要があります。一度あなたがそれらの内部構造を突き詰めれば、それは完璧な意味を持っていますが、それは毎日素人になってしまいます。それはまったく直感的ではありません。
追加された 著者 Mark Ransom,
追加された 著者 martineau,
この動作は「デザインの選択」ではなく、可能な限り例外を除いて、単純な作業原則から始まる言語の仕組みの結果です。ある時点では、私が「Pythonで考える」ようになったとき、この動作はちょうど自然になりました - それが起こらなかったら私は驚くでしょう
追加された 著者 jsbueno,
5年後:OK。このバグの唯一の良い使い方はmemoizationであり、とにかく @ functools.lru_cache で行うことができるようです。一口...
追加された 著者 Eric Duminil,
@ MarkRansom:良い例。これは正確でシンプルに見えますが、それは他の多くの言語でも動作しますが、Pythonでは悲惨に失敗します。
追加された 著者 Eric Duminil,
@ MarkRansom:あなたの定義では、(決定論的な)コンピュータ上にこれまでどんなバグもありません。あなたが内部をgrokkingするのに十分な時間を過ごすと、すべてのバグは理にかなっています。正直言って、この動作をPythonの設計上のわずかな欠陥の1つと呼んでみましょう。
追加された 著者 Eric Duminil,
追加された 著者 sdolan,
私が知っている最も良い説明は、リンクされた質問にあります。関数は、クラスのようなファーストクラスのオブジェクトです。クラスには属性データが変更可能です。関数には可変のデフォルト値があります。
追加された 著者 Katriel,

6 答え

関数呼び出し間で値をキャッシュするために使用することができます。

def get_from_cache(name, cache={}):
    if name in cache: return cache[name]
    cache[name] = result = expensive_calculation()
    return result

そのようなことは通常、キャッシュをクリアするために追加の属性を持つことができるように、クラスでうまくいっています。

29
追加された
@katrielalex lru_cacheはPython 3.2で新しくなっているので、誰もそれを使うことはできません。
追加された 著者 Duncan,
...またはメモを付けるデコレータ。
追加された 著者 Daniel Roseman,
@ functools.lru_cache(maxsize = None)
追加された 著者 Katriel,
参考までにbackports.functools_lru_cacheがあります。 pypi.python.org/pypi/backports.functools_lru_cache
追加された 著者 Panda,
import random

def ten_random_numbers(rng=random):
    return [rng.random() for i in xrange(10)]

random モジュールを効果的に変更可能なシングルトンとして、デフォルトの乱数ジェネレータとして使用します。

4
追加された
しかし、これは非常に重要なユースケースでもありません。
追加された 著者 Evgeni Sergeev,

Canonical answer is this page: http://effbot.org/zone/default-values.htm

変更可能なデフォルト引数の3つの「良い」ユースケースについても言及しています。

  • コールバックの外部変数の現在値にローカル変数をバインドする
  • キャッシュ/メモ
  • グローバル名のローカル再バインド(高度に最適化されたコード用)
4
追加された
このリンクは質問に答えるかもしれませんが、答えの本質的な部分をここに含めて参照のためのリンクを提供する方が良いでしょう。リンクされたページが変更された場合、リンクのみの回答は無効になります。 - レビューから
追加された 著者 Dijkgraaf,

多分あなたは可変引数を突然変異させないかもしれませんが、可変引数を期待するでしょう:

def foo(x, y, config={}):
    my_config = {'debug': True, 'verbose': False}
    my_config.update(config)
    return bar(x, my_config) + baz(y, my_config)

(この特定のケースでは config =()を使用できることはわかっていますが、あまり明確ではなく一般的ではありません)。

3
追加された

EDIT(明確化):変更可能なデフォルト引数の問題は、より深い設計選択の徴候であり、すなわち、デフォルトの引数値が関数オブジェクトの属性として格納される。あなたはなぜこの選択​​がなされたのか尋ねるかもしれません。いつものように、そのような質問は適切に答えるのが難しいです。しかし、それは確かに良い用途があります:

パフォーマンスの最適化:

def foo(sin=math.sin): ...

変数の代わりにクロージャのオブジェクト値を取得する。

callbacks = []
for i in range(10):
    def callback(i=i): ...
    callbacks.append(callback)
1
追加された
@ウォルフラム:真:P! 2つはしばしば一致しますが。
追加された 著者 Katriel,
また@WolframH。
追加された 著者 Katriel,
@ジョナサン:私のポイントは、これらは変更可能ではありません。 Pythonがデフォルト引数をコンパイル時に定義された関数オブジェクトに格納するために使用するシステムは便利です。これは、それぞれの関数呼び出しで引数を再評価すると、そのトリックが役に立たなくなるため、デフォルトの引数が変更可能であるということを意味します。
追加された 著者 Katriel,
整数と組み込み関数は変更できません!
追加された 著者 WolframH,
@ジョナサン:まだ残っている例では、デフォルトの引数を変更することはできませんか、それとも見えませんか?
追加された 著者 WolframH,
@katriealex:OK、あなたの答えでは、引数の再評価が必要であると仮定し、それが悪い理由を示していると言います。 Nit-pick:デフォルトの引数値は、コンパイル時には格納されませんが、関数定義文が実行されるときには格納されません。
追加された 著者 WolframH,

何らかの理由で必要な場合は、パラメータが指定されていなければ、リストを空にしてください。

def bad_append(new_item, a_list=[]):
        bad_append.__defaults__ = ([],)
        a_list.append(new_item)
        return a_list
0
追加された