1

I declare three instances of MyClass. I want to store each of these instances in a list variable instances. Before the instance is appended to the instances I double check that it is not already in a list. Interesting, that all three instances appear to be exactly the same to Python. Why is it happening and what should we know to avoid surprises like this in a future?

instances=[]
for i in range(3):
    instance=MyClass(i)
    for each in instances:
        if each==instance:
            print 'THE SAME?', each.ID,'==', instance.ID
    if instance not in instances:
        print "Instance %s is not in instances"%instance.ID
        instances.append(instance)
    else:
        print "Instance %s is already in instances"%instance.ID 
        index=instances.index(instance)
        instanceFromList=instances[index]
        print "COMPARE: instance.ID:", instance.ID, '   instanceFromList.ID:', instanceFromList.ID  

print 'Number of instances stored:', len(instances)

OUTPUT:

Instance 0 is not in instances
THE SAME? 0 == 1
Instance 1 is already in instances
COMPARE: instance.ID: 1    instanceFromList.ID: 0
THE SAME? 0 == 2
Instance 2 is already in instances
COMPARE: instance.ID: 2    instanceFromList.ID: 0

Number of instances stored: 1
alphanumeric
  • 17,967
  • 64
  • 244
  • 392
  • 2
    `in` operator checks for **identity** and then **equality**, and in this case they are equal. Define your custom `__eq__` and `__ne__` methods. Method used: [`PyObject_RichCompareBool `](https://docs.python.org/2/c-api/object.html#c.PyObject_RichCompareBool) – Ashwini Chaudhary Aug 20 '15 at 21:26
  • just edited my example to check with `==` operator. It misleads as well... – alphanumeric Aug 20 '15 at 21:34
  • @Sputnix Why is it misleading? This is how lists behave. Two empty lists are equal. It's not going to use an arbitrary property you added unless you override the equality tests yourself and tell it to do so. – Two-Bit Alchemist Aug 20 '15 at 21:35

2 Answers2

2

Because the lists are empty, they compare as equal. This is because the comparison used to determine if each instance is in the "instances" list is a value comarison, and not an identity comparison.

If you were to add something to the MyClass instance with ID=1 before adding it to the "instances" list, then your code would add the empty MyClass instance with ID=2 as well.

Example without using a subtype of list:

>>> a = list()
>>> b = list()
>>> a == b  # equal value
True
>>> a is b
False  # different identity
>>> c = list()
>>> c.append(a)
>>> a in c
True
>>> b in c
True
>>> a.append("a value")
>>> a == b  # now the values differ
False
>>> a in c  # The value of 'a' changed, and 'a' is still in 'c'
True
>>> b in c  # 'b' is still empty, and there's no empty list in 'c'
False

To support the "MyClass in list" comparison based on the ID instance variable, you would have to implement __eq__ and __ne__ comparison methods in your class. These methods should then compare the types of the instances and their ID.

Note that if you implement this, you would also lose the ability to compare the contents of the MyClass instances.

fredrikhl
  • 159
  • 4
1

I'm not sure why you expect this to behave differently than a regular list:

instances = []
for i in range(3):
    instance = []
    if instance not in instances:
        print 'Adding instance to list...'
        instances.append(instance)
    else:
        print 'Instance is already present in list'
        print instances

print 'Number of instances stored:', len(instances)

This prints:

Adding instance to list...
Instance is already present in list
[[]]
Instance is already present in list
[[]]
Number of instances stored: 1

If you want it to somehow differentiate your list subclass by taking into account the id you gave it, you'll have to define that behavior yourself by overriding how your object responds to equality tests.

Two-Bit Alchemist
  • 17,966
  • 6
  • 47
  • 82