I am purposefully proposing this for slices as opposed to ranges because it is about the bounds of the slices, not the items in synthetic sequences. Also, slices can refer to any type of value, not just integers.
Presumably, these operations would raise exceptions when used with slices that have `step` values other than `None`. Alternatively, those could hypothetically be valid in restricted cases such as when all properties are either int or Fraction types. Probably better to have them be simply unsupported though.
a in b # True if <a> is fully contained within <b>
a.intersects(b) # True if any value could be within both <a> and <b>
a & b # Intersection of <a> and <b> or None if no intersection
a | b # Union of <a> and <b> or Exception if neither contiguous nor overlapping.
Also, it might be nice to be able to test whether a non-slice value falls within the slice's bounds. This would be using `x in s` as a shorthand for `s.start <= x < s.end`. Again, this is different than asking whether a value is "in" a range because a rage is a sequence of discrete integers whereas a slice represents a possibly continuous range of any kind of value to which magnitude is applicable.
slice(1, 2) in (0, 3)
# => True because 1 >= 0 and 2 <= 3
slice(0.5, 1.5) in slice(0, 2)
# => True because 0.5 >= 1.5 and 0.5 < 2
1 in (0, 3)
# => True because 0 <= 1 < 3
'Joe' in slice('Alice', 'Riley')
# => True because 'Alice' <= 'Joe' < 'Riley'
slice(1.1, 5.9).intersects(slice(2, 10.5))
# => True because either...
# 1.1 <= 2 < 5.9 or
# 1.1 < 10.5 < 5.5 or
# 2 <= 1.1 < 10.5 or
# 2 < 5.9 < 10.5
slice(5.5, 15.5) & slice(10.25, 20.25)
# => slice(10.25, 15.5)
slice(5.5, 15.5) | slice(10.25, 20.25)
# => slice(5.5, 20.25)
slice('abc', 'fff') & slice('eee', 'xyz')
# => slice('fff', 'eee')