2009-10-31 3 views
6

次のコードブロック1が出力2の代わりに出力1になるのはなぜですか?Python Scoping/Static Misunderstanding

コードブロック1:

class FruitContainer: 
     def __init__(self,arr=[]): 
      self.array = arr 
     def addTo(self,something): 
      self.array.append(something) 
     def __str__(self): 
      ret = "[" 
      for item in self.array: 
       ret = "%s%s," % (ret,item) 
      return "%s]" % ret 

arrayOfFruit = ['apple', 'banana', 'pear'] 
arrayOfFruitContainers = [] 

while len(arrayOfFruit) > 0: 
    tempFruit = arrayOfFruit.pop(0) 
    tempB = FruitContainer() 
    tempB.addTo(tempFruit) 
    arrayOfFruitContainers.append(tempB) 

for container in arrayOfFruitContainers: 
    print container 

**Output 1 (actual):** 
[apple,banana,pear,] 
[apple,banana,pear,] 
[apple,banana,pear,] 

**Output 2 (desired):** 
[apple,] 
[banana,] 
[pear,] 

このコードの目的は、配列を反復し、親オブジェクトの各ラップすることです。これは私の実際のコードを減らしたもので、すべてのリンゴをリンゴの袋などに加えています。私の推測は、何らかの理由で、同じオブジェクトを使用しているか、フルーツコンテナが静的配列を使用しているかのように動作していることです。私はこれをどのように修正するのか分かりません。

+1

質問に対する回答ではなく、注目に値する: "while len(arrayOfFruit)> 0:"は "while arrayOfFruit:"と同じです。Pythonスタイルガイドによれば、後者が望ましい。 –

答えて

2

あなたのコードは、クラスを初期化するデフォルトの引数があります。解決策は以下の通り__init__の機能を変更することです。デフォルトの引数の値はコンパイル時に1回評価されるため、すべてのインスタンスは同じリストで初期化されます。そのようにそれを変更します。

def __init__(self, arr=None): 
    if arr is None: 
     self.array = [] 
    else: 
     self.array = arr 

私はここに、より完全にこれを議論:なしに渡すよりもHow to define a class in Python

8

メソッドのデフォルト引数に変更可能な値([]など)を使用しないでください。この値は1回計算され、すべての呼び出しに使用されます。既定値として空のリストを使用すると、前の関数呼び出しによって値が変更されても、引数なしでメソッドが呼び出されるたびに同じリストが使用されます。

代わりにこれを行います:ネッドが言うように

def __init__(self,arr=None): 
    self.array = arr or [] 
+0

完璧!これは幻想的でシンプルです。 –

+4

値がfalseと評価されるのを見て、 'None'をテストするのは本当に好きではありません。 'isなし'テストを使う方が良いです。呼び出し元は合法的に初期化子の空リストを渡すことができ、あなたのコードはその空のリストを破棄して空のリストを新しく作成します...これは、クラスのいくつかのインスタンスをすべて共有しようとすると最初は空リストだったと思いますが、可能です。いずれにせよ、 'None'をテストするために' is'を使うのは良い習慣です。 – steveha

+0

あなたは正しいです、 'isはNone'です。 –

1

、問題はあなたがデフォルトの引数としてリストを使用しています。詳細はhereです。

 def __init__(self,arr=None): 
      if arr is not None: 
       self.array = arr 
      else: 
       self.array = [] 
0

よりよい解決策 - この特定のインスタンスでは、むしろ一般的にはよりを - にARRのパラメータを処理することです__init__の項目の列挙セットが内部ストレージに使用するとFruitContainerはなく、配列を事前に初期化するよう:これはあなたのコンテナを初期化するために、あなたは他の列挙タイプに渡すことができるようになります

class FruitContainer: 
    def __init__(self, arr=()): 
    self.array = list(arr) 
    ... 

、より高度なPythonのユーザーが行うことができることを期待なる:それはまた別の潜在的なエイリアシングバグ打開ます

myFruit = ('apple', 'pear') # Pass a tuple 
myFruitContainer = FruitContainer(myFruit) 
myOtherFruit = file('fruitFile', 'r') # Pass a file 
myOtherFruitContainer = FruitContainer(myOtherFruit) 

:このページのすべての他の実装では

myFruit = ['apple', 'pear'] 
myFruitContainer1 = FruitContainer(myFruit) 
myFruitContainer2 = FruitContainer(myFruit) 
myFruitContainer1.addTo('banana') 
'banana' in str(myFruitContainer2) 

を、これはTrueを返します、ので、誤ってコンテナの内部ストレージに別名を付けたことがあります。

注:このアプローチは正しい答えではない常にです:「ないなし場合は、」他の例では、より良いではありません。私はオブジェクトのセット、または変更可能なコンテナを渡していますか?オブジェクトを渡しているクラス/関数が、私が与えたストレージを変更する場合、それは(a)驚くか、(b)望ましいでしょうか?この場合、私はそれが(a)であると主張します。したがって、リスト(...)呼び出しが最善の解決策です。 (b)の場合、「もしそうでなければ」は正しいアプローチであろう。

+1

こんにちは、私は "誰か"と思います。私はこのようにこの例をコーディングすることに異論はありません。この例では、フルーツを渡して、FruitContainerクラスの内部ストレージに追加します。ユーザーは、いくつかの果物でコンテナを初期化するほどリストオブジェクトを渡すことはありません。さて、一般的なケースでは、ユーザーが提供するものを静かに強要するのは良い考えではないと思います。私たちがお互いに話し合っていた理由は、私が一般的なケースに焦点を当てていて、あなたはこの非常に特殊なケースを考えていたと思います。 *一般的には、強制的なタイプのダックタイピングは中断しないでください。 – steveha

+1

優れています。私はこれを反映するために最後の段落を書き直しました。私にあなたのポイントを明確にする時間をとってくれてありがとう! –

+0

私はあなたが早くどこから来ていたのか分かりませんでした。私はフル・パンダ・モードだったと思う。 :-) – steveha