1

重複の可能性:
Python の「最小の驚き」: 変更可能な既定の引数

授業のために

class ValidationResult():
    def __init__(self, passed=True, messages=[], stop=False):
        self.passed = passed
        self.messages = messages
        self.stop = stop

ランニング

foo = ValidationResult()
bar = ValidationResult() 
foo.messages.append("Foos message")  
print foo.messages
print bar.messages

生産する

['Foos message']
['Foos message']

まだこれ

foo = ValidationResult()
bar = ValidationResult(messages=["Bars message"]) 
foo.messages.append("Foos message")  
print foo.messages
print bar.messages

生産する

['Foos message']
['Bars message']

ここでインスタンス属性を理解するのに船を逃したと思います。最初のサンプルでは、Foos message​​ にのみ適用されると予想していましたfoo。インスタンスによってのみ変更可能なオブジェクト属性を宣言する正しい方法は何ですか?

Python 2.7.1 の使用

4

5 に答える 5

2

ここで何が起こっているかを見ることができます:

>>> class ValidationResult():
...     def __init__(self, passed=True, messages=[], stop=False):
...         self.passed = passed
...         self.messages = messages
...         print id(self.messages)
...         self.stop = stop
... 
>>> foo = ValidationResult()
4564756528
>>> bar = ValidationResult()
4564756528

デフォルトの引数は、常にメモリ内の同じオブジェクトです。リストの簡単な回避策の 1 つは、インスタンス化ごとにリストのコピーを作成することです。

>>> class ValidationResult():
...     def __init__(self, passed=True, messages=[], stop=False):
...         self.passed = passed
...         self.messages = messages[:]
...         print id(self.messages)
...         self.stop = stop
... 
>>> foo = ValidationResult()
4564756312
>>> bar = ValidationResult()
4564757032
于 2012-05-31T12:18:02.183 に答える
1

引数のデフォルト値として使用される空のリストmessagesは、グローバル変数です。したがって、最初の例でfoo.messages is bar.messagesは True ですが、2 番目の例では youが Truemessages=["Bars message"]になりbar.messages is not foo.messagesます。これは最も古典的なトラップです。

于 2012-05-31T12:16:32.263 に答える
1

これは python 関数のちょっとした癖で、クラスがなくても同じように見ることができます:

def foo(bar=[]):
    bar.append('boo')
    print bar

foo()
foo()

「問題」は、モジュールのロード時にデフォルトの引数 (bar) が作成されることです。 他の何かを明示的に渡さなければ、同じオブジェクトが foo のデフォルト引数として引き続き渡されます。

変更可能なデフォルト引数を使用する標準的な方法は、ユーザーが何も渡さなかったことを示すために演算子をNone使用してテストできるセンチネル値 (通常は ) を使用することです (もちろん、関数でデフォルト引数を変更することが必要な場合を除きます)。is例えば:

def foo(bar=None):
    if(bar is None):
       bar=[]
    bar.append('boo')
    print bar

ドキュメントへのリンクは次のとおりです。「重要な警告」セクションに注意してください。

于 2012-05-31T12:14:21.897 に答える
0

これself.messagesは、デフォルト パラメータのエイリアスです。デフォルトのパラメーターは、呼び出し元ではなく、関数で構築されます。

class ValidationResult():
    def __init__(self, passed=True, messages=None, stop=False):
        self.passed = passed
        self.messages = messages if messages is not None else []
        self.stop = stop

>>> foo = ValidationResult()
>>> bar = ValidationResult() 
>>> foo.messages.append("Foos message")  
>>> print foo.messages
['Foos message']
>>> print bar.messages
[]
于 2012-05-31T12:17:13.847 に答える
0

いいえ、クラス属性とインスタンス属性は関係ありません。

問題は、Python でのすべての代入は単にオブジェクトへの参照をバインドしているだけであるため、messagesパラメーターとして使用された値が属性__init__として格納された値になってしまうことです。messagesコピーされません。これは、その値が他の場所でも使用され、変更できる場合に問題になりmessagesます。属性によって参照されるオブジェクトへの変更が、同じオブジェクトへの他の参照に影響を与えるためです。

messagesパラメータ toのデフォルト値があるため、値を指定__init__せずに呼び出すたびに、同じオブジェクトが入力されます。デフォルト値はデフォルトであり、新しい値を作成するためのレシピではないため、デフォルト値が使用されるたびに同じリストを取得します。

人々はこれを「変更可能なデフォルト引数」の問題と呼んでいますが、それよりも一般的な問題だと思います。明示的にパスすると、簡単に噛む可能性がmessagesあります。実際の問題は、入力パラメーターを変更していることです (インスタンス属性に参照を格納し、それを変更することによって)。オブジェクトを変更することが関数の目的でない限り、これは決して良い考えではありません。これは通常、コンストラクターには当てはまらないため、リストを変更する予定がある場合は、リストをコピーする必要があります。

于 2012-05-31T12:27:18.403 に答える