Python“奇怪”的输出

时间:2010-10-11 10:42:56

标签: python

class Foo(object):
    def __init__(self,x):
        self.x = x
        self.is_bar = False
    def __repr__(self): return str(self.x)

class Bar(object):
    def __init__(self,l = []):
        self.l = l
    def add(self,o):
        self.l += [o]
    def __repr__(self): return str(self.l)

def foo_plus_foo(f1,f2):
    t = Bar()
    if not (f1.is_bar and f2.is_bar):
        f1.is_bar = True
        f2.is_bar = True
        t.add(f1)
        t.add(f2)
        print 'HERE'
    return t

if __name__ == '__main__':
    li = [Foo(1), Foo(2)]
    print foo_plus_foo(li[0],li[1])
    print foo_plus_foo(li[0],li[1])

意外的输出:

HERE
[1, 2]
[1, 2]

预期输出

HERE
[1, 2]
[]

发生了什么事?我做错了什么?为什么python使用旧值?我该怎么做才能避免这种情况?

谢谢!

4 个答案:

答案 0 :(得分:5)

从不。做。此

def __init__(self,l = []):

从不。

重用一个list对象。并且它是可变的,因此每次重用时,都会更新方法定义中创建的唯一[]

始终。做。此

def __init__( self, l= None ):
    if l is None: l = []

这会创建一个全新的,唯一的列表实例。

答案 1 :(得分:4)

您将l定义为默认值[]。 这是classic Python pitfall

class Bar(object):
    def __init__(self,l = []):

默认值的评估时间为定义时间而不是运行时。 它仅评估一次。

所以t=Bar()每次都会将t.l设置为同一个列表。

要解决此问题,请将栏更改为

class Bar(object):
    def __init__(self,l = None):
        if l is None:
            l=[]
        self.l = l
    def add(self,o):
        self.l += [o]
    def __repr__(self): return str(self.l)

答案 2 :(得分:1)

罪魁祸首是Bar类定义中定义的 l = [] 。 此类列表在类定义期间实例化一次,并用作默认值。 超级危险!!我和其他许多人都被这个人烧伤了,相信我的伤痕很深。

有问题地使用mutable。

class Bar(object):
    def __init__(self,l = []):
        self.l = l
    def add(self,o):
        self.l += [o]
    def __repr__(self): return str(self.l)

尝试使用不可变的:

class Bar(object):
    def __init__(self,l = None):
        if l is None:
            self.l = []
        else:
            self.l = l
    def add(self,o):
        self.l += [o]
    def __repr__(self): return str(self.l)

答案 3 :(得分:1)

其他人已经解释了这个问题并建议使用l=None并对其进行明确的测试。我想提出一个不同的方法:

class Bar(object):
    def __init__(self, l=[]):
        self.l = list(l)
        # and so on...

这默认每次都保证一个新的空白列表,并且还确保如果调用者传入他们自己的列表,您将获得该列表的副本,而不是对调用者列表的引用。作为一个额外的好处,调用者可以传入列表构造函数可以使用的任何内容,例如元组或字符串,并且您的代码不必担心这一点;它可以处理一个列表。

如果您只是保存对调用者提供给您的列表的引用,并稍后更改名为self.l的列表,您可能无意中更改了传入的列表(因为您的类和调用者都是现在有一个对同一个对象的引用)。同样,如果他们在调用构造函数后更改了列表,那么您的“副本”也将被更改(因为它实际上不是副本)。当然,这可能是你想要的行为,但可能不是这种情况。

虽然如果你从不操纵列表(即添加,替换或删除项目),但只是引用它或批量替换它,复制它通常是浪费时间和内存。

使用list()构造函数创建的副本是浅。也就是说,列表本身是一个新对象,但如果原始列表包含对可变对象的引用(例如其他列表)或者字典,或大多数其他类的实例),如果更改这些对象,可能会出现同样的问题,因为它们仍然在列表之间共享。这个问题的频率低于您的想象,但如果是这样,您可以使用deepcopy()模块中的copy函数执行深层复制。