Can someone help me with this bug?
Peter Otten
__peter__ at web.de
Thu May 27 15:51:38 EDT 2004
Duncan Smith wrote:
> I'm probably missing something that should be obvious, but can
> anyone tell me what's wrong with the following?
>
> temp.py
> ---------------------------------------------------------------------
>
> class Item(object):
> def __init__(self, id, data):
> self.id = id
> self.data = data
>
>
> class KeyedSet(dict):
> def __init__(self, items=None):
> if items is not None:
> for item in items:
> self[item.id] = item
>
> def __iter__(self):
> return self.itervalues()
>
> def __repr__(self):
> return '%s(%r)' % (self.__class__.__name__, self.keys())
>
> def __contains__(self, item):
> return self.has_key(item.id)
>
> def intersection(self, other):
> res = self.__class__()
> for item in self:
> if item in other:
> res[item.id] = item
> return res
>
> def __and__(self, other):
> return self.intersection(other)
>
> def intersection_update(self, other):
> self &= other
>
> def __iand__(self, other):
> self = self.intersection(other)
> return self
> --------------------------------------------------------------------
>
>>>> from temp import Item, KeyedSet
>>>> a = Item(0, 'W')
>>>> b = Item(1, 'X')
>>>> c = Item(2, 'Y')
>>>> d = Item(3, 'Z')
>>>> aset = KeyedSet([a, b, c])
>>>> bset = KeyedSet([b, c, d])
>>>> aset &= bset
>>>> aset
> KeyedSet([1, 2])
>>>> aset = KeyedSet([a, b, c])
>>>> aset.intersection_update(bset)
>>>> aset
> KeyedSet([0, 1, 2])
>>>>
>
> I can't figure out why 'aset' is unchanged? Thanks.
Your problem stripped down to the bare bones: __iand__() creates a new
instance instead of modifying the current one.
self = something
doesn't copy something's data to self, it just rebinds self to something for
the rest of the method.
A minimal example of what went wrong:
>>> class A:
... def __init__(self, value):
... self.value = value
... def __iand__(self, other):
... return A(self.value + other.value)
... def __repr__(self):
... return "value=%s" % self.value
...
>>> a = b = A(1)
>>> a &= A(2)
It looks like inplace modification, but isn't:
>>> a, b
(value=3, value=1)
a is rebound to the newly created instance, which tricks you into believing
it was modified. The backup reference b reveals the error.
Here's the correction (I'm lazy, so I reuse the unaltered parts of A):
>>> class B(A):
... def __iand__(self, other):
... self.value += other.value
... return self
...
>>> a = b = B(1)
>>> a &= B(2)
>>> a, b
(value=3, value=3)
Once you understand the basic principle, it should be no problem to apply it
to your KeyedSet class. If in doubt, use the sets.Set implementation as a
template.
Peter
More information about the Python-list
mailing list