views:

252

answers:

3

When comparing a tuple with a list like ...

>>> [1,2,3] == (1,2,3)
False
>>> [1,2,3].__eq__((1,2,3))
NotImplemented
>>> (1,2,3).__eq__([1,2,3])
NotImplemented

... Python does not deep-compare them as done with (1,2,3) == (1,2,3).

So what is the reason for this? Is it because the mutable list can be changed at any time (thread-safety issues) or what?

(I know where this is implemented in CPython, so please don't answer where, but why it is implemented.)

+6  A: 

You can always "cast" it

>>> tuple([1, 2]) == (1, 2)
True

Keep in mind that Python, unlike for example Javascript, is strongly typed, and some (most?) of us prefer it that way.

voyager
That really is the best way.
jathanism
I like to think that most of us prefer it that way.
Quintin Robinson
I think the idea of strong typing is what really matters here, so I'll accept your answer as correct.
AndiDog
Types is types. Just because list and tuple are both "sequences" doesn't really mean much. You can't compare a file and a tuple, and they're also both iterable sequences.
S.Lott
Although 1 == 1.0 even though they are different types.
Paul Hankin
@Paul Hankin: Coercion rules are an odd thing. Arithmetic coercion is marginally tolerable. More complex, non-scalar type conversion is almost impossible to justify because of the ad-hoc rules that have to get invented.
S.Lott
@S.Lott And what about str/unicode or set/frozenset? Isn't the relation between set and frozenset exactly the same as that between list and tuple?
Paul Hankin
A: 

I would have to imagine it is because the tuple is immutable and as such is stored as a different datatype. Doing a compare on different datatypes to evaluate equality without explicitly trying to convert doesn't really sound like desired functionality in general anyway right?..

Quintin Robinson
+3  A: 

There's no technical reason for lists not being able to compare to tuples; it's entirely a design decision driven by semantics. For proof that it's not related to thread-safety, you can compare lists to other lists:

>>> l1 = [1, 2, 3]
>>> l2 = [1, 2, 3]
>>> l1 == l2
True
>>> id(l1) == id(l2)
False

It seems reasonable to allow users to directly compare lists and tuples, but then you end up with other questions: should the user be allowed to compare lists and queues? What about any two objects which provide iterators? What about the following?

>>> s = set([('x', 1), ('y', 2)])
>>> d = dict(s)
>>> s == d  # This doesn't work
False

It can get complicated pretty quickly. The language designers recognized the issue, and avoided it by simply preventing different collection types from comparing directly with each other1.

Note that the simple solution (to create a new list from the tuple and compare them) is easy but inefficient. If you're working with large numbers of items, you're better off with something like:

def compare_sequences(iter1, iter2):
    iter1, iter2 = iter(iter1), iter(iter2)
    for i1 in iter1:
        try:
            i2 = next(iter2)
        except StopIteration:
            return False

        if i1 != i2:
            return False

    try:
        i2 = next(iter2)
    except StopIteration:
        return True

    return False

This has the advantage of working on any two sequences, at an obvious cost in complexity.


1 I note there's an exception for sets and frozensets. And no doubt a few others I'm not aware of. The language designers are purists, except where it pays to be practical.

Chris B.
+1 Very good points, thanks!
AndiDog
To compare two sequences, there's no need to jump through next/StopIteration hoops.all(i1 == i2 for i1, i2 in itertools.izip_longest(iter1, iter2, fillvalue=object()))
Paul Hankin