Python "strange" output
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__(开发者_如何学Cself): 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])
UNEXPECTED OUTPUT:
HERE
[1, 2]
[1, 2]
EXPECTED OUTPUT:
HERE
[1, 2]
[]
What is happening? What did I do wrong? Why is python using old value? What do I do to avoid this?
Thanks!
Never. Do. This.
def __init__(self,l = []):
Never.
One list
object is reused. And it's Mutable, so that each time it's reused, the one and only []
created in your method definition is updated.
Always. Do. This.
def __init__( self, l= None ):
if l is None: l = []
That creates a fresh, new, unique list instance.
You are defining l
as having a default value of []
.
This is a classic Python pitfall.
class Bar(object):
def __init__(self,l = []):
Default values are evaluated at definition time not run-time. It is evaluated only once.
So t=Bar()
sets t.l
to the very same list every time.
To fix this, change Bar to
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)
The culprit is the l=[] defined in the Bar class definition. This list is instantiated once during class definition and is used as the default. Super dangerous!! I and many others have been burned by this one, trust me the scarring is deep.
Problematic use of 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)
Try using an immutable:
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)
Others have explained the problem and suggested using l=None
and an explicit test for it. I'd like to suggest a different approach:
class Bar(object):
def __init__(self, l=[]):
self.l = list(l)
# and so on...
This guarantees a new blank list each time by default, and also assures that if a caller passes in a list of their own, you get a copy of that list rather than a reference to the caller's list. As an added benefit, callers can pass in anything that the list constructor can consume, such as a tuple or a string, and your code doesn't have to worry about that; it can just deal with a list.
If you just save a reference to the list the caller gives you, and later change the list named self.l
, you may be inadvertently changing the list that was passed in, too (since both your class and the caller now have a reference to the same object). Similarly, if they change the list after calling your constructor, your "copy" will be changed too (since it's not actually a copy). Of course, it could be that this is behavior you want, but probably not in this case.
Although if you never manipulate the list (i.e. add, replace, or delete items), but only refer to it or replace it wholesale, copying it is usually a waste of time and memory.
The copy made using the list()
constructor is shallow. That is, the list itself is a new object, but if the original list contains references to mutable objects (such as other lists or dictionaries, or instances of most other classes), the same problem can arise if you change those objects, because they are still shared between the lists. This is an issue less frequently than you might think, but if it is, you can perform a deep copy using the deepcopy()
function in the copy
module.
精彩评论