Python可変デフォルト引数:なぜ?


20

デフォルトの引数は、関数が呼び出されるたびにではなく、関数の初期化時に作成されることを知っています。次のコードを参照してください。

def ook (item, lst=[]):
    lst.append(item)
    print 'ook', lst

def eek (item, lst=None):
    if lst is None: lst = []
    lst.append(item)
    print 'eek', lst

max = 3
for x in xrange(max):
    ook(x)

for x in xrange(max):
    eek(x)

これがこのように実装された理由はわかりません。この動作は、各呼び出し時の初期化に対してどのような利点がありますか?


これについては、スタックオーバーフローに関する驚くべき詳細で既に説明しています。stackoverflow.com
q

回答:


14

その理由は実装が簡単だからだと思います。詳しく説明させてください。

関数のデフォルト値は、評価する必要がある式です。あなたの場合、それはクロージャーに依存しない単純な式ですが、自由変数を含むものにすることができます- def ook(item, lst = something.defaultList())。Pythonを設計する場合、選択肢があります。関数を定義するときに1回評価するのか、関数を呼び出すたびに評価するのですか。Pythonは最初のものを選択します(2番目のオプションを使用するRubyとは異なります)。

これにはいくつかの利点があります。

まず、速度とメモリの向上が得られます。ほとんどの場合、不変のデフォルト引数があり、Pythonはすべての関数呼び出しではなく、一度だけそれらを構築できます。これにより、メモリと時間を節約できます。もちろん、可変の値ではうまく機能しませんが、どうやって回避できるか知っています。

もう1つの利点は、シンプルさです。式がどのように評価されるかを理解するのは非常に簡単です-関数が定義されるとき、レキシカルスコープを使用します。逆の場合、定義と呼び出しの間で字句スコープが変更され、デバッグが少し難しくなる可能性があります。これらの場合、Pythonは非常に簡単になります。


3
興味深い点-それは通常、Pythonで最も驚きの少ない原則です。いくつかのことは、モデルの形式的な複雑さという意味では単純ですが、それでも非自明で驚くことではありません。これは重要だと思います。
Steve314

1
ここで最も驚くべきことはこれです:毎回評価するセマンティクスがある場合、2つの関数呼び出し間でクロージャーが変更されるとエラーが発生する可能性があります(かなり可能です)。これは、一度評価されることを知るのとは対照的に、より驚くべきことです。もちろん、他の言語から来たとき、あなたは各呼び出しごとに評価するセマンティクスを除いて、それは驚きであると主張することができますが、それが両方の方法に行く方法を見ることができます:)
Stefan Kanev

スコープについての良い点
0xc0de

実際には、スコープがより重要だと思います。デフォルトの定数に制限されていないため、呼び出しサイトのスコープ内にない変数が必要になる場合があります。
マークランサム

5

それを置く1つの方法は、パラメーターを変更lst.append(item) しないlstことです。lstまだ同じリストを参照しています。そのリストの内容が変更されただけです。

基本的に、Pythonには定数または不変の変数はまったくありません(覚えています)が、一定の不変の型があります。整数値を変更することはできません。置き換えることしかできません。ただし、リストの内容を置き換えることなく変更できます。

整数と同様に、参照を変更することはできません。置換することしかできません。ただし、参照されているオブジェクトのコンテンツは変更できます。

デフォルトのオブジェクトを1回作成することに関しては、オブジェクト作成とガベージコレクションのオーバーヘッドを節約するために、それが主に最適化であると思います。


+1正確に。インダイレクションの層を理解することは重要です-変数値ではありません。代わりに、値を参照します。変数を変更するには、値をすることができスワップ、または変異した(それが可変の場合)。
ジョナスプラッカ

pythonの変数に関係するトリッキーなものに直面したとき、「=」を「名前バインディング演算子」と見なすと便利です。バインドしようとしているものが新しい(新しいオブジェクトまたは不変型インスタンス)かどうかに関係なく、名前は常にリバウンドされます。
StarWeaver 14

4

この動作は、各呼び出し時の初期化に対してどのような利点がありますか?

例で示したように、必要な動作を選択できます。したがって、デフォルトの引数を不変にする場合は、またはなどの不変の値を使用します。デフォルトの引数を可変にしたい場合は、などの可変のものを使用します。確かに柔軟性ですが、知らない場合は噛むことができます。None1[]


2

本当の答えは次のとおりだと思います:Pythonは手続き型言語として記述されており、事後的に機能的な側面のみを採用しています。あなたが探しているのは、パラメータのデフォルト設定がクロージャとして行われることであり、Pythonのクロージャは実際には中途半端です。この試みの証拠:

a = []
for i in range(3):
    a.append(lambda: i)
print [ f() for f in a ]

これにより[2, 2, 2]、真のクロージャが生成されると予想される場所が得られます[0, 1, 2]

Pythonにクロージャーでパラメーターのデフォルト設定をラップする機能があれば、非常に多くのものが欲しいです。例えば:

def foo(a, b=a.b):
    ...

非常に便利ですが、「a」は関数定義時にスコープ内にないため、それを行うことはできず、代わりに不格好な処理を行う必要があります。

def foo(a, b=None):
    if b is None:
        b = a.b

これはほとんど同じことです...ほとんど。



1

大きな利点はメモ化です。これは標準的な例です:

def fibmem(a, cache={0:1,1:1}):
    if a in cache: return cache[a]
    res = fib(a-1, cache) + fib(a-2, cache)
    cache[a] = res
    return res

比較のために:

def fib(a):
    if a == 0 or a == 1: return 1
    return fib(a-1) + fib(a-2)

ipythonでの時間測定:

In [43]: %time print(fibmem(33))
5702887
CPU times: user 0 ns, sys: 0 ns, total: 0 ns
Wall time: 200 µs

In [43]: %time print(fib(33))
5702887
CPU times: user 1.44 s, sys: 15.6 ms, total: 1.45 s
Wall time: 1.43 s

0

これは、記述コードを実行することでPythonでのコンパイルが実行されるために発生します。

言ったら

def f(x = {}):
    ....

毎回新しい配列が必要であることは明らかです。

しかし、私が言う場合:

list_of_all = {}
def create(stuff, x = list_of_all):
    ...

ここでは、さまざまなリストにアイテムを作成し、リストを指定しない場合は単一のグローバルキャッチオールを作成したいと思います。

しかし、コンパイラはこれをどのように推測しますか?なぜ試してみますか?これに名前が付けられているかどうかに頼ることができ、時には役立つかもしれませんが、実際には推測に過ぎません。同時に、試行しない正当な理由があります-一貫性。

そのままで、Pythonはコードを実行するだけです。変数list_of_allには既にオブジェクトが割り当てられているため、このオブジェクトは、関数の呼び出しがここで指定されたローカルオブジェクトへの参照を取得するのと同じ方法で、デフォルトでxをコードに渡します。

名前のないケースと名前のあるケースを区別したい場合、コンパイル時にコードが実行時に実行されるのとは大幅に異なる方法で割り当てを実行することになります。したがって、特別なケースは作成しません。


-5

これは、Pythonの関数がファーストクラスオブジェクトであるために発生します

関数定義が実行されると、デフォルトのパラメーター値が評価されます。これは、関数が定義されたときに式が1回評価され、同じ「事前に計算された」値が各呼び出しに使用されることを意味します

パラメーター値を編集すると、後続の呼び出しのデフォルト値が変更されること、および関数本体で明示的なテストを行い、デフォルトとしてNoneを使用する簡単な解決策だけで、驚きがないことを確認できます。

これは、def foo(l=[])呼び出されるとその関数のインスタンスになり、以降の呼び出しで再利用されることを意味します。関数パラメーターは、オブジェクトの属性の一部になると考えてください。

Proには、これを利用してクラスにCのような静的変数を持たせることができます。したがって、デフォルト値Noneを宣言し、必要に応じて初期化するのが最善です。

class Foo(object):
    def bar(self, l=None):
        if not l:
            l = []
        l.append(5)
        return l

f = Foo()
print(f.bar())
print(f.bar())

g = Foo()
print(g.bar())
print(g.bar())

収量:

[5] [5] [5] [5]

予想外の代わりに:

[5] [5、5] [5、5、5] [5、5、5、5]


5
いいえ。関数(ファーストクラスまたはそうでない)を別々に定義して、呼び出しごとにデフォルトの引数式を再度評価することができます。そして、その後のすべて、つまり回答の約90%は完全に問題の横にあります。-1

1
そこで、各呼び出しのデフォルト引数を評価する関数を定義する方法に関するこの知識を共有してください。PythonDocsが推奨するよりも簡単な方法を知りたいです。
反転

2
言語設計レベルでは、つまり。Python言語の定義では、現在、デフォルトの引数はそのままの形で扱われると述べています。同様に、デフォルトの引数が他の方法で処理されることを明記できます。IOWは、「なぜ物事がそうであるのか」という質問に対して「物事はそうである」と答えています。

Pythonは、Coffeescriptが行う方法と同様のデフォルトパラメータを実装できます。バイトコードを挿入して欠落しているパラメーターを確認し、欠落している場合は式を評価します。
ウィンストンイーバート
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.