instance variable weirdness

W

wietse

Hello,

I have written the following script to illustrate a problem in my code:

class BaseClass(object):
def __init__(self):
self.collection = []

class MyClass(BaseClass):
def __init__(self, name, collection=[]):
BaseClass.__init__(self)
self.name = name
self.collection = collection

if __name__ == '__main__':
seq = []
inst = None
for i in xrange(5):
inst = MyClass(str(i))
inst.collection.append(i)
seq.append(inst)
inst = None
for i in xrange(5):
inst = MyClass(str(i)+'~', [])
inst.collection.append(i)
seq.append(inst)
inst = None
for i in seq:
print "Instance '%s'; collection = %s" % (i.name,
str(i.collection))

The output I get is:Instance '0'; collection = [0, 1, 2, 3, 4]
Instance '1'; collection = [0, 1, 2, 3, 4]
Instance '2'; collection = [0, 1, 2, 3, 4]
Instance '3'; collection = [0, 1, 2, 3, 4]
Instance '4'; collection = [0, 1, 2, 3, 4]
Instance '0~'; collection = [0]
Instance '1~'; collection = [1]
Instance '2~'; collection = [2]
Instance '3~'; collection = [3]
Instance '4~'; collection = [4]
I don't understand why the first loop doesn't give the same result as
the second loop. Can somebody enlighten me?

Wietse
 
F

Felipe Almeida Lessa

Em Sex, 2006-04-14 às 09:18 -0700, wietse escreveu:
def __init__(self, name, collection=[]):

Never, ever, use the default as a list.
self.collection = collection

This will just make a reference of self.collection to the collection
argument.
inst.collection.append(i)

As list operations are done in place, you don't override the
self.collection variable, and all instances end up by having the same
list object.

To solve your problem, change
def __init__(self, name, collection=[]):
BaseClass.__init__(self)
self.name = name
self.collection = collection # Will reuse the list
to
def __init__(self, name, collection=None):
BaseClass.__init__(self)
self.name = name
if collection is None:
collection = [] # Will create a new list on every instance
self.collection = collection
 
F

Felipe Almeida Lessa

Em Sex, 2006-04-14 às 13:30 -0300, Felipe Almeida Lessa escreveu:
To solve your problem, change
def __init__(self, name, collection=[]):
BaseClass.__init__(self)
self.name = name
self.collection = collection # Will reuse the list
to
def __init__(self, name, collection=None):
BaseClass.__init__(self)
self.name = name
if collection is None:
collection = [] # Will create a new list on every instance
self.collection = collection

Or if None is valid in your context, do:

__marker = object()
def __init__(self, name, collection=__marker):
BaseClass.__init__(self)
self.name = name
if collection is __marker:
collection = [] # Will create a new list on every instance
self.collection = collection
 
S

Steven D'Aprano

Em Sex, 2006-04-14 às 09:18 -0700, wietse escreveu:
def __init__(self, name, collection=[]):

Never, ever, use the default as a list.

Unless you want to use the default as a list.

Sometimes you want the default to mutate each time it is used, for example
that is a good technique for caching a result:

def fact(n, _cache=[1, 1, 2]):
"Iterative factorial with a cache."
try:
return _cache[n]
except IndexError:
start = len(_cache)
product = _cache[-1]
for i in range(start, n+1):
product *= i
_cache.append(product)
return product
 
F

Felipe Almeida Lessa

Em Sáb, 2006-04-15 às 04:03 +1000, Steven D'Aprano escreveu:
Sometimes you want the default to mutate each time it is used, for example
that is a good technique for caching a result:

def fact(n, _cache=[1, 1, 2]):
"Iterative factorial with a cache."
try:
return _cache[n]
except IndexError:
start = len(_cache)
product = _cache[-1]
for i in range(start, n+1):
product *= i
_cache.append(product)
return product

I prefer using something like this for the general case:

def cached(function):
"""Decorator that caches the function result.

There's only one caveat: it doesn't work for keyword arguments.
"""
cache = {}
def cached_function(*args):
"""This is going to be replaced below."""
try:
return cache[args]
except KeyError:
cache[args] = function(*args)
return cache[args]
cached_function.__doc__ = function.__doc__
cached_function.__name__ = function.__name__
return cached_function



And for this special case, something like:

def fact(n):
"Iterative factorial with a cache."
cache = fact.cache
try:
return cache[n]
except IndexError:
start = len(cache)
product = cache[-1]
for i in range(start, n+1):
product *= i
cache.append(product)
return product
fact.cache = [1, 1, 2]



This may be ugly, but it's less error prone. Also, we don't expose the
cache in the function's argument list.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,774
Messages
2,569,596
Members
45,137
Latest member
NoelAshwor
Top