_AssociationSet has odd __eq__ behaviour

Issue #3265 resolved
Other User created an issue

If I have association proxies for sets, I noticed that __eq__ does't behave as expected. Namely, two empty association sets are not considered equal, nor is an association set equal to itself.

This results from the implementation:

    def __eq__(self, other):
        return set(self) == other

If we reverse the terms, and instead use:

    def __eq__(self, other):
        return other == set(self)

Then the comparison of set_a == set_b, will end up doing set_a.__eq__(set_b) --> set_b.__eq__(set(a)) --> set(a).__eq__(set(b)) which seems to be the desired behavior. This same fix would be applied to most, if not all, of the comparison operators.

Though, set() == empty_association_set will still be False... but there isn't much you can do about that.

See the attached example using the User, Keyword example of AssociationProxy.

Comments (5)

  1. Mike Bayer repo owner
    • changed milestone to 1.1

    this can be 1.0 if you can send me a PR to do it this way, including tests in test/ext/test_associationproxy.py:

    diff --git a/lib/sqlalchemy/ext/associationproxy.py b/lib/sqlalchemy/ext/associationproxy.py
    index 1aa68ac..64675c1 100644
    --- a/lib/sqlalchemy/ext/associationproxy.py
    +++ b/lib/sqlalchemy/ext/associationproxy.py
    @@ -1024,10 +1024,12 @@ class _AssociationSet(_AssociationCollection):
             return set(self)
    
         def __eq__(self, other):
    -        return set(self) == other
    +        return set(self) == set(other)
    
         def __ne__(self, other):
    -        return set(self) != other
    +        return set(self) != set(other)
    +
    +    # etc.
    
         def __lt__(self, other):
             return set(self) < other
    

    it's quite the gotcha that sets are so different than lists/dicts:

    class ListLike(object):
        def __init__(self):
            self.data = [1,2,3]
    
        def __getitem__(self, index):
            return self.data[index]
    
        def __iter__(self):
            return iter(self.data)
    
        def __eq__(self, other):
            return list(self) == other
    
    print [1,2,3] == ListLike()  # OK!
    
    
    class SetLike(object):
        def __init__(self):
            self.data = [1,2,3]
    
        def __getitem__(self, index):
            return self.data[index]
    
        def __iter__(self):
            return iter(self.data)
    
        def __eq__(self, other):
            return set(self) == other
    
    print set([1,2,3]) == SetLike()  # Nope!
    

    ideally I'd like to get these to work more in the "official" ABC way (see https://docs.python.org/2/library/collections.html#collections-abstract-base-classes), however we tried to do that in a simple way once and things just broke all over the place, so that would need a lot more effort.

  2. Mike Bayer repo owner

    so. huge surprise on this one. This seems to be a Python 2.6 bug; running the attached script:

    [classic@dell sqlalchemy]$ /opt/python2.6/bin/python test.py 
    set_0: set([])
    set_a: set([])
    set_b: set([])
    (set_a == set_a) == False?
    (set_a == set_b) == False?
    (set_a == set_0) == True
    (set_0 == set_a) == False
    Patching _AssociationSet.__eq__
    (set_a == set_a) == True
    (set_a == set_b) == True
    (set_a == set_0) == True
    (set_0 == set_a) == False
    [classic@dell sqlalchemy]$ /opt/python2.7/bin/python test.py 
    set_0: set([])
    set_a: set([])
    set_b: set([])
    (set_a == set_a) == True?
    (set_a == set_b) == True?
    (set_a == set_0) == True
    (set_0 == set_a) == True
    Patching _AssociationSet.__eq__
    (set_a == set_a) == True
    (set_a == set_b) == True
    (set_a == set_0) == True
    (set_0 == set_a) == True
    

    So, that's a real surprise. Going to see if I can find anything that refers to this but this issue is kind of a "worksforme" considering we are dropping py2.6 in any case.

  3. Mike Bayer repo owner

    Add tests for empty association set comparison

    This seems to only occur in python 2.6, adding tests to ensure it stays

    Change-Id: Id714680970bf1f70e2fe06b0c8688b7c5a6b6b0c Fixes: #3265

    → <<cset 0612829fb045>>

  4. Log in to comment