Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't Python3's range/slice object support containment testing for other ranges/slices?

Python3's range objects support O(1) containment checking for integers (1) (2)

So one can do 15 in range(3, 19, 2) and get the correct answer True

However, it doesn't support containment checking b/w two ranges

a = range(0, 10)
b = range(3, 7)
a in b # False
b in a # False, even though every element in b is also in a
a  < b # TypeError: '<' not supported between instances of 'range' and 'range'

It seems that b in a is interpreted as is any element in the range 'a' equal to the object 'b'?

However, since the range cannot contain anything but integers, range(...) in range(...) will always return False. IMHO, such a query should be answered as is every element in range 'b' also in range 'a'? Given that range only stores the start, stop, step and length, this query can also be answered in O(1).

The slice object doesn't help either. It doesn't implement the __contains__ method, and the __lt__ method simply compares two slices as tuples (which makes no sense)

Is there a reason behind the current implementation of these, or is it just a "it happened to be implemented this way" thing?

like image 879
Pragy Agarwal Avatar asked Mar 08 '23 08:03

Pragy Agarwal


1 Answers

It looks like the implementation of __contains__ for ranges is range_contains, which just checks if the given element is in the iterable, with a special case for longs.

As you have correctly observed, e in b returns true iff e is an element in b. Any other implementation, such as one that checks if e is a subset of b, would be ambiguous. This is especially problematic in Python, which doesn't require iterables be homogenous, and allows nested lists/tuples/sets/iterables.

Consider a world where your desired behavior was implemented. Now, suppose we have the following list:

my_list = [1, 2, (3, 4), 5]
ele1 = (3, 4)
ele2 = (2, 5)

In this case, what should ele1 in my_list and ele2 in my_list return? Your desired behavior makes it tedious to write code such as

if e in my_iterable:
    # ah! e must exist!
    my_iterable.remove(e)

A safer, better way is to keep the current behavior, and instead use a different type-sensitive operator to implement subset predicates:

x = set([1])
y = set([1,2])
x < y  # True
[1] < y  # raises a TypeError
like image 161
James Lim Avatar answered Apr 06 '23 06:04

James Lim