rjurney
rjurney

Reputation: 5160

Python class: Why can't I use the method len() inside __eq__(self, other)?

https://gist.github.com/rjurney/1e8454af8e44312d02d7

class FrozenSortedTuple:
  """A frozenset that cares about order of tuples. And is not actually frozen."""
  def __init__(self, vals):
    if type(vals) not in [list, set, tuple]:
      raise Exception('Value not a list or set')
    self.vals = list(vals)

  def __eq__(self, other):
    if len(self.vals) != len(other):
      return False
    if type(self.vals) != type(other):
      return False
    if type(other) not in [list, set, tuple]:
      return False
    other_list = list(other)
    for i,item in enumerate(self.vals):
      if item != other_list[i]:
        return False
    return True

Called in iPython:

In [2]: a = ['a','b']

In [3]: b = ['b','a']

In [4]: c = ['a','b']

In [5]: a == b
Out[5]: False

In [6]: FrozenSortedTuple(a)
Out[6]: <__main__.FrozenSortedTuple instance at 0x103c56200>

In [7]: fa = FrozenSortedTuple(a)

In [8]: fb = FrozenSortedTuple(b)

In [9]: fa == fb

Error:

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-9-317181571e4d> in <module>()
----> 1 fa == fb

<ipython-input-1-ef99f0af5061> in __eq__(self, other)
     15 
     16   def __eq__(self, other):
---> 17     if len(self.vals) != len(other):
     18       return False
     19     if type(self.vals) != type(other):

AttributeError: FrozenSortedTuple instance has no attribute '__len__'

I'm confused.

Upvotes: 2

Views: 834

Answers (3)

rjurney
rjurney

Reputation: 5160

Thanks, based on the other answers, what i wanted is this:

class FrozenSortedTuple:
  """A frozenset that cares about order of tuples. And is not actually frozen."""
  def __init__(self, vals):
    if type(vals) not in [list, set, tuple]:
      raise Exception('Value not a list or set')
    self.vals = vals

  def __len__(self):
    return len(self.vals)

  def __iter__(self):
    return iter(self.vals)

  def __getitem__(self, key):
    return list(self.vals)[key]

  def __eq__(self, other):
    if len(self) != len(other):
      print "len(self)"
      return False

    for i,item in enumerate(self.vals):
      if item != other[i]:
        return False
    return True

  def __str__(self):
    str_val = str()
    for val in self:
      str_val += str(val)
    return str_val

  def __hash__(self):
    return hash(str(self))

Tests:

# FrozenSortedTuple Tests
a = ['a','b']
b = ['b','a']
c = ['a','b']

fa = FrozenSortedTuple(a)
fb = FrozenSortedTuple(b)
fc = FrozenSortedTuple(c)

fa == fb
fa == fc
fa == ['a','b']
fa == ['b','a']
fa == ('a','b')
fa == ('b','a')

a = set([fa, fb, fc])
b = set([fa, fb, fc])
c = set([fa, fc, fb])
a == b
b == c
fa in a
fb in b

d = set([fb])
fa in d

Upvotes: 1

Malte
Malte

Reputation: 789

The way you define __eq__(self, other), equality can only be achieved when other is an instance of the type you are wrapping, i.e. list, set, or tuple. You trigger the error by comparing two instances of FrozenSortedTuple. The error message tells you that len() cannot be invoked on such an instance, and that is because you have not defined the method __len__(self) in your class.

If you define __len__() for your class, it will work. See the Python Documentation (this links to the 2.7 docs, but it should work the same in Python 3.x) Alternatively, you could compare len(self.vals) == len(other.vals) to compare FrozenSortedTuple instances.

Upvotes: 2

Kevin
Kevin

Reputation: 76244

If you're trying to directly compare the structure and contents of the values of two FrozenSortedTuples, change all your instances of other to other.vals.

def __eq__(self, other):
    if len(self.vals) != len(other.vals):
      return False
    if type(self.vals) != type(other.vals):
      return False
    if type(other.vals) not in [list, set, tuple]:
      return False
    other_list = list(other.vals)
    for i,item in enumerate(self.vals):
      if item != other_list[i]:
        return False
    return True

Of course, this will fail to work if other isn't a FrozenSortedTuple. For instance, fa == 23 won't work because the number 23 doesn't have a "vals" attribute.

Upvotes: 3

Related Questions