Python作用域/静态错误

Python作用域/静态错误,python,class,scope,iteration,static-members,Python,Class,Scope,Iteration,Static Members,我真的很想知道为什么下面的代码块1会导致输出1而不是输出2 代码块1: class FruitContainer: def __init__(self,arr=[]): self.array = arr def addTo(self,something): self.array.append(something) def __str__(self): ret = "["

我真的很想知道为什么下面的代码块1会导致输出1而不是输出2

代码块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,]

这段代码的目标是迭代一个数组,并将每个数组包装到父对象中。这是我实际代码的一个缩减,它将所有苹果添加到一袋苹果中,以此类推。我的猜测是,出于某种原因,它要么使用相同的对象,要么就好像水果容器使用静态数组一样。我不知道如何解决这个问题。

永远不要将可变值(如[])用作方法的默认参数。该值计算一次,然后用于每次调用。当使用空列表作为默认值时,每次在没有参数的情况下调用该方法时都会使用相同的列表,即使该值是由以前的函数调用修改的

改为这样做:

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

正如Ned所说,问题是您使用列表作为默认参数。还有更多细节。解决方案是将
\uuuu init\uuuu
函数更改如下:

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

您的代码有一个初始化类的默认参数。默认参数的值在编译时计算一次,因此每个实例都使用相同的列表初始化。这样改变它:

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

我在这里更详细地讨论了这一点:

比不传入更好的解决方案-在这个特定的实例中,而不是一般情况下-是将arr参数uuu init_uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu

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);因此,list(…)调用是最好的解决方案。如果(b),“如果没有”将是正确的方法。

不是对您问题的回答,但也值得注意:“while len(arrayOfFruit)>0:”相当于“while arrayOfFruit:”。至少根据Python风格指南,后者更可取。我真的不希望看到您通过查看值是否为false来测试
None
。最好使用
is None
测试。调用方可以合法地为初始值设定项传递一个空列表,您的代码将丢弃该空列表并生成一个新的空列表。。。我想,只有当有人试图使该类的多个实例共享相同的初始空列表时,才会出现这种情况,但这是可能的。在任何情况下,使用
is
测试
None
都是一个好习惯。哦,一个更好的例子说明了你的代码可能会失败:有人可以用一些期望的额外属性创建list的子类,将其作为初始值传递,如果它计算为false,你的代码将放弃它,而选择一个空的标准列表。更好的是,保留arr=[]并使用self.array=list(arr)。目前,用户无法在不中断类的情况下将生成器或元组传递给构造函数。@chrispy,这是个坏主意。如果用户希望将迭代器扩展为列表,则可以调用
FruitContainer(list(iterable))
。但是,如果用户希望传入列表的子类,并带有副作用日志记录之类的内容,该怎么办?您的
列表(arr)
将摆脱子类对象,并强制它成为一个无聊的列表。有一个标准的、经过时间检验的Python习惯用法,它是对
is None
的测试,然后存储提供的参数。如果它不是
None
@steveha,那么用户想关心类的内部存储的可能性有多大?您好,我想我是“某人”。我不反对以这种方式编写这个示例。在本例中,您正在传递水果,以便添加到水果容器类中的内部存储中。用户与其说是传入列表对象,不如说是用一些水果初始化容器。现在,在一般情况下,我认为无声地强制用户提供的东西不是一个好主意。我想我们之所以不谈,是因为我关注的是一般情况,而你考虑的是这个非常具体的情况。一般来说,不要通过强制类型来破坏duck类型。非常好。为了反映这一点,我重写了最后一段。谢谢你花时间让我更清楚地了解你的观点!我只是很抱歉我没早点弄明白你是从哪里来的。我想我当时完全处于学究模式。:-)