28

私の一連の「python初心者の質問」に従い、別の質問に基づいています。

特権

http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html#other-languages-have-variablesに移動し、「デフォルト パラメータ値」までスクロールします。そこには、次のものがあります。

def bad_append(new_item, a_list=[]):
    a_list.append(new_item)
    return a_list

def good_append(new_item, a_list=None):
    if a_list is None:
        a_list = []
    a_list.append(new_item)
    return a_list

これとまったく同じ例でpython.orgに「重要な警告」さえありますが、実際には「より良い」とは言っていません。

それを置く1つの方法

ここでの質問は、「エレガントな構文」と「使いやすい」を促進するプログラミング言語のように、既知の問題に対する「良い」構文がなぜ醜いのかということです。

編集:

別の言い方

なぜ、どのように起こるのかは尋ねていません(リンクをくれた Mark に感謝します)。

言語に組み込まれているより単純な代替手段がない理由を尋ねています。

より良い方法はおそらくそれ自体で何かを行うことができると思います.namedef引数は、変更可能なオブジェクト内の「ローカル」または「新しい」にアタッチされdefます。何かのようなもの:

def better_append(new_item, a_list=immutable([])):
    a_list.append(new_item)
    return a_list

誰かがより良い構文を提供できると確信していますが、これが行われていない理由については、非常に適切な説明があるに違いないと思います。

4

8 に答える 8

11

これは、「変更可能なデフォルト トラップ」と呼ばれます。参照: http://www.ferg.org/projects/python_gotchas.html#contents_item_6

基本的にa_list、関数を呼び出すたびに初期化されるのではなく、プログラムが最初に解釈されるときに初期化されます (他の言語から予想されるように)。したがって、関数を呼び出すたびに新しいリストを取得するわけではありませんが、同じリストを再利用しています。

質問に対する答えは、リストに何かを追加したい場合は、それを行うだけで、それを行う関数を作成しないということだと思います。

これ:

>>> my_list = []
>>> my_list.append(1)

以下よりも明確で読みやすい:

>>> my_list = my_append(1)

実際のケースでは、この種の動作が必要な場合は、内部リストを管理するメソッドを持つ独自のクラスを作成することになるでしょう。

于 2010-04-14T18:24:17.527 に答える
6

デフォルトの引数は、ステートメントの実行時に評価されますdef。これは、おそらく最も合理的なアプローチです。多くの場合、必要なものです。そうでない場合、環境が少し変化すると、混乱を招く結果が生じる可能性があります。

魔法のlocal方法などで差別化するのは理想からは程遠いです。Pythonは物事をかなりわかりやすくしようとしますが、Pythonが現在持っているかなり一貫したセマンティクスをいじることに頼らない、現在の定型文に明確で明確な代替手段はありません。

于 2010-04-14T18:31:07.613 に答える
4

オプションで変更するリストを渡すことができるが、特にリストを渡さない限り新しいリストを生成する関数の非常に特殊なユースケースは、特別な場合の構文の価値がないことは間違いありません。真剣に、この関数を何度も呼び出す場合、シリーズの最初の呼び出しを他のすべての呼び出し(2つの引数が必要になる)と区別するために(1つの引数のみを渡すことによって)特殊なケースにする必要があるのはなぜですか?既存のリストを充実させ続けることができるように)?! たとえば、次のようなものを考えてみてください(もちろん、それが何か便利なことをしたと仮定しbetterappendます。現在の例では、直接の代わりにそれを呼び出すのはおかしいから.appendです!-):

def thecaller(n):
  if fee(0):
    newlist = betterappend(foo())
  else:
    newlist = betterappend(fie())
  for x in range(1, n):
    if fee(x):
      betterappend(foo(), newlist)
    else:
      betterappend(fie(), newlist)

これは単に正気ではなく、明らかに、代わりに、

def thecaller(n):
  newlist = []
  for x in range(n):
    if fee(x):
      betterappend(foo(), newlist)
    else:
      betterappend(fie(), newlist)

常に2つの引数を使用し、繰り返しを避け、はるかに単純なロジックを構築します。

特殊なケースの構文を導入すると、特殊なケースのユースケースが奨励およびサポートされます。この非常に特殊なユースケースを推奨およびサポートすることにはあまり意味がありません。既存の完全に規則的な構文は、ユースケースの非常にまれな良い使用法には問題ありません。 )。

于 2010-04-14T18:33:39.123 に答える
3

この回答を編集して、質問に投稿された多くのコメントからの考えを含めました。

あなたが与える例には欠陥があります。副作用として渡すリストを変更します。それが関数の動作を意図した方法である場合、デフォルトの引数を持つことは意味がありません。また、更新されたリストを返すことも意味がありません。デフォルトの引数がなければ、問題は解決します。

新しいリストを返すことが意図されていた場合は、入力リストのコピーを作成する必要があります。Python は物事が明示的であることを好むので、コピーを作成するのはあなた次第です。

def better_append(new_item, a_list=[]): 
    new_list = list(a_list)
    new_list.append(new_item) 
    return new_list 

少し違うものとして、リストまたはジェネレーターを入力として受け取ることができるジェネレーターを作成できます。

def generator_append(new_item, a_list=[]):
    for x in a_list:
        yield x
    yield new_item

あなたは、Python が変更可能なデフォルト引数と不変のデフォルト引数を異なる方法で扱うという誤解をしていると思います。それは単に真実ではありません。むしろ、引数の不変性により、コードを微妙な方法で変更して、正しいことを自動的に行うことができます。例を挙げて、リストではなく文字列に適用します。

def string_append(new_item, a_string=''):
    a_string = a_string + new_item
    return a_string

このコードは、渡された文字列を変更しません。文字列は不変であるため、変更できません。新しい文字列を作成し、その新しい文字列に a_string を割り当てます。デフォルトの引数は変更されないため、何度でも使用できます。最初にコピーを作成しました。

于 2010-04-14T20:39:03.553 に答える
2

リストについてではなく、定義したばかりのクラスであるAwesomeSetsについて話している場合はどうでしょうか。すべてのクラスで「.local」を定義しますか?

class Foo(object):
    def get(self):
        return Foo()
    local = property(get)

おそらくうまくいくかもしれませんが、本当にすぐに、本当にすぐに古くなるでしょう。間もなく、「aがNoneの場合:a = CorrectObject()」パターンが第二の性質になり、醜いものではなく、明るくなります。

問題は構文の1つではなく、セマンティクスの1つです。デフォルトのパラメーターの値は、関数の実行時ではなく、関数の定義時に評価されます。

于 2010-04-14T18:31:32.777 に答える
1

おそらく、これら 2 つの機能を善悪で定義するべきではありません。リストまたは辞書で最初のものを使用して、対応するオブジェクトの変更をインプレースで実装できます。変更可能なオブジェクトがどのように機能するかを知らない場合、この方法は頭痛の種になる可能性がありますが、自分が何をしているのかを知っていれば、私の意見では問題ありません。

したがって、異なる動作を提供するパラメーターを渡すには、2 つの異なる方法があります。そして、これは良いことです。私はそれを変更しません。

于 2010-04-14T18:29:03.393 に答える
0

エレガントな構文とシンタックスシュガーを混同していると思います。Python構文は、両方のアプローチを明確に伝達します。正しいアプローチは、誤ったアプローチよりも(構文の行に関して)エレガントではないように見えることがあります。しかし、間違ったアプローチはよく...間違っているので、それは優雅さは無関係です。better_appendで示したようなものが実装されていない理由についてはThere should be one-- and preferably only one --obvious way to do it.、エレガンスのわずかな向上よりも優れていると思います。

于 2010-04-14T18:42:41.957 に答える
0

これは good_append() よりも優れています、IMO:

def ok_append(new_item, a_list=None):
    return a_list.append(new_item) if a_list else [ new_item ]

また、細心の注意を払って、 a_list がリストであることを確認することもできます...

于 2010-04-14T19:09:43.493 に答える