I am working in a domain in which ranges are conventionally described inclusively. I have human-readable descriptions such as from A to B , which represent ranges that include both end points - e.g. from 2 to 4 means 2, 3, 4.
What is the best way to work with these ranges in Python code? The following code works to generate inclusive ranges of integers, but I also need to perform inclusive slice operations:
def inclusive_range(start, stop, step):
return range(start, (stop + 1) if step >= 0 else (stop - 1), step)
The only complete solution I see is to explicitly use + 1 (or - 1) every time I use range or slice notation (e.g. range(A, B + 1), l[A:B+1], range(B, A - 1, -1)). Is this repetition really the best way to work with inclusive ranges?
Edit: Thanks to L3viathan for answering. Writing an inclusive_slice function to complement inclusive_range is certainly an option, although I would probably write it as follows:
def inclusive_slice(start, stop, step):
...
return slice(start, (stop + 1) if step >= 0 else (stop - 1), step)
... here represents code to handle negative indices, which are not straightforward when used with slices - note, for example, that L3viathan's function gives incorrect results if slice_to == -1.
However, it seems that an inclusive_slice function would be awkward to use - is l[inclusive_slice(A, B)] really any better than l[A:B+1]?
Is there any better way to handle inclusive ranges?
Edit 2: Thank you for the new answers. I agree with Francis and Corley that changing the meaning of slice operations, either globally or for certain classes, would lead to significant confusion. I am therefore now leaning towards writing an inclusive_slice function.
To answer my own question from the previous edit, I have come to the conclusion that using such a function (e.g. l[inclusive_slice(A, B)]) would be better than manually adding/subtracting 1 (e.g. l[A:B+1]), since it would allow edge cases (such as B == -1 and B == None) to be handled in a single place. Can we reduce the awkwardness in using the function?
Edit 3: I have been thinking about how to improve the usage syntax, which currently looks like l[inclusive_slice(1, 5, 2)]. In particular, it would be good if the creation of an inclusive slice resembled standard slice syntax. In order to allow this, instead of inclusive_slice(start, stop, step), there could be a function inclusive that takes a slice as a parameter. The ideal usage syntax for inclusive would be line 1:
l[inclusive(1:5:2)] # 1
l[inclusive(slice(1, 5, 2))] # 2
l[inclusive(s_[1:5:2])] # 3
l[inclusive[1:5:2]] # 4
l[1:inclusive(5):2] # 5
Unfortunately this is not permitted by Python, which only allows the use of : syntax within []. inclusive would therefore have to be called using either syntax 2 or 3 (where s_ acts like the version provided by numpy).
Other possibilities are to make inclusive into an object with __getitem__, permitting syntax 4, or to apply inclusive only to the stop parameter of the slice, as in syntax 5. Unfortunately I do not believe the latter can be made to work since inclusive requires knowledge of the step value.
Of the workable syntaxes (the original l[inclusive_slice(1, 5, 2)], plus 2, 3 and 4), which would be the best to use? Or is there another, better option?
Final Edit: Thank you all for the replies and comments, this has been very interesting. I have always been a fan of Python's "one way to do it" philosophy, but this issue has been caused by a conflict between Python's "one way" and the "one way" proscribed by the problem domain. I have definitely gained some appreciation for TIMTOWTDI in language design.
For giving the first and highest-voted answer, I award the bounty to L3viathan.