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使用旧值?我该怎么做才能避免这种情况?
谢谢!
答案 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
函数执行深层复制。