Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Check if object is in list (not "by value", but by id)

Consider the following code:

>>> class A(object):
...   def __init__(self, a):
...     self.a = a
...   def __eq__(self, other):
...     return self.a==other.a
... 
>>> a=A(1)
>>> b=A(1)
>>> c=A(2)

>>> a==b
True                   # because __eq__ says so
>>> a==c
False                  # because __eq__ says so
>>> a is b
False                  # because they're different objects

>>> l = [b,c]
>>> a in l
True                   # seems to use __eq__ under the hood

So, in seems to use __eq__ to determine whether or not something is in a container.

  1. Where can one find documentation on this behavior?
  2. Is it possible to make in use object identity, a.k.a. a in somelist if the object a is in somelist, and not some other object that compares equal to a?
like image 404
Jasper Avatar asked Dec 28 '15 18:12

Jasper


Video Answer


2 Answers

Use the any() function and a generator expression:

any(o is a for o in l)

The behaviour of in is documented in the Common Sequence Operators section:

x in s
True if an item of s is equal to x, else False

Bold emphasis mine.

If you must use in, use a wrapper object with a custom __eq__ method that uses is, or build your own container where a custom __contains__ method uses is to test against each contained element.

The wrapper could look like this:

class IdentityWrapper(object):
    def __init__(self, ob):
        self.ob = ob
    def __eq__(self, other):
        return other is self.ob

Demo:

>>> IdentityWrapper(a) in l
False
>>> IdentityWrapper(a) in (l + [a])
True

The container could just use the same any() function outlined above:

class IdentityList(list):
    def __contains__(self, other):
        return any(o is other for o in self)

Demo:

>>> il = IdentityList(l)
>>> a in il
False
>>> a in IdentityList(l + [a])
True
like image 79
Martijn Pieters Avatar answered Sep 30 '22 08:09

Martijn Pieters


If you do not want to change A behaviour, you may prepare thin wrapper for used container. To change how in operator behaves, magic method __contains__ needs to get overridden. Quoting docs:

Called to implement membership test operators. Should return true if item is in self, false otherwise. For mapping objects, this should consider the keys of the mapping rather than the values or the key-item pairs.

Sample code:

class A(object):
    def __init__(self, a):
        self.a = a

    def __eq__(self, other):
        return self.a == other.a


class IdentityList(list):
    def __contains__(self, obj):
        return any(o is obj for o in self)

a = A(1)
b = A(1)
c = A(2)
container = [b, c]
identity_container = IdentityList(container)
assert a in container  # not desired output (described in question)
assert a not in identity_container  # desired output
like image 40
Łukasz Rogalski Avatar answered Sep 30 '22 08:09

Łukasz Rogalski