Using continuous ranges

class Range(self, *args)

A class representing a range from a start value to an end value. The start and end need not be numeric, but they must be comparable. A Range is immutable, but not strictly so - nevertheless, it should not be modified directly.

A new Range can be constructed in several ways:

  1. From an existing Range object

>>> a = Range()   # range from -infinity to +infinity (see point #4 for how "infinity" works here)
>>> b = Range(a)  # copy of a
  1. From a string, in the format “[start, end)”.

Both ‘()’ (exclusive) and ‘[]’ (inclusive) are valid brackets, and start and end must be able to be parsed as floats. Brackets must be present (a ValueError will be raised if they aren’t).

Also, the condition start <= end must be True.

If constructing a Range from a string, then the keyword arguments include_start and include_end will be ignored.

>>> c = Range("(-3, 5.5)")  # Infers include_start and include_end to both be false
>>> d = Range("[-3, 5.5)")  # Infers include_start to be true, include_end to be false
  1. From two positional arguments representing start and end.

start and end may be anything so long as the condition start <= end is True and does not error. Both start and end must be given together as positional arguments; if only one is given, then the program will try to consider it as an iterable.

>>> e = Range(3, 5)
>>> f = Range(3, 5, include_start=False, include_end=True)
>>> print(e)  # [3, 5)
>>> print(f)  # (3, 5]
  1. From the keyword arguments start and/or end.

start and end may be anything so long as the condition start <= end is True and does not error. If not provided, start is set to -infinity by default, and end is set to +infinity by default. If any of the other methods are used to provide start and end values for the Range, then these keywords will be ignored.

>>> g = Range(start=3, end=5)  # [3, 5)
>>> h = Range(start=3)  # [3, inf)
>>> i = Range(end=5)  # [-inf, 5)
>>> j = Range(start=3, end=5, include_start=False, include_end=True)  # (3, 5]

You can also use the default infinite bounds with other types:

>>> k = Range(start=datetime.date(1969, 10, 5))  # [1969-10-05, inf)   includes any date after 5/10/1969
>>> l = Range(end="ni", include_end=True)  # [-inf, ni)   all strings lexicographically less than 'ni'

When constructing a Range in any way other than via a string, non-numeric values may be used as arguments. This includes dates:

>>> import datetime
>>> m = Range(datetime.date(1478, 11, 1), datetime.date(1834, 7, 15))
>>> print(datetime.date(1492, 8, 3) in m)  # True
>>> print(datetime.date(1979, 8, 17) in m)  # False

and strings (using lexicographic comparisons):

>>> n = Range("killer", "rabbit")
>>> print("grenade" in n)  # False
>>> print("pin" in n)  # True
>>> print("three" in n)  # False

or any other comparable type.

By default, the start of a range (include_start) will be inclusive, and the end of a range (include_end) will be exclusive. User-given values for include_start and include_end will override these defaults.

The Range data structure uses a special notion of “infinity” that works with all types, not just numeric ones. This allows for endless ranges in datatypes that do not provide their own notions of infinity, such as datetimes. Be warned that a range constructed without arguments will then contain every value that can possibly be contained in a Range:

>>> q = Range(include_end=True)
>>> print(q)  # [-inf, inf]
>>> print(0 in q)  # True
>>> print(1 in q)  # True
>>> print(-99e99 in q)  # True
>>> print("one" in q)  # True
>>> print(datetime.date(1975, 3, 14) in q)  # True
>>> print(None in q)  # True

Although, for numeric types, infinity automatically conforms to the mathematical infinity of IEEE 754:

>>> print(float('nan') in q)  # False

Mathematically, infinity and negative infinity would always be exclusive. However, since they are defined values in the floating- point standard, they follow the same rules here as any other value, with regard to inclusivity or exclusivity in Range objects:

>>> r = Range(include_start=True, include_end=False)
>>> print(r)  # [-inf, inf)
>>> print(float('-inf') in r)  # True
>>> print(float('inf') in r)  # False

The Range class is hashable, meaning it can be used as the key in a dict.

__init__(self, *args)

Constructs a new Range from start to end, or from an existing range. Is inclusive on the lower bound and exclusive on the upper bound by default, but can be made differently exclusive by setting the keywords include_start and include_end to True or False.

Can be called in the fallowing ways: >>> Range(2, 5) # two arguments, start and end respectively >>> Range(‘[2..5)’) # one argument, of type String - resolves to a numeric range. Use ‘..’ or ‘,’ as separator >>> Range(Range(2, 5)) # one argument, of type Range - copies the given Range >>> Range(start=2, end=5) # keyword arguments specify start and end. If not given, they default to -Inf/Inf >>> Range() # no arguments - infinite bounds by default

If using the constructor Range(‘[2, 5)’), then the created range will be numeric. Otherwise, the Range may be of any comparable type - numbers, strings, datetimes, etc. The default Infinite bounds will safely compare with any type, even if that type would not normally be comparable.

Positional arguments for start and end will take priority over keyword arguments for start and end, if both are present.

Additionally, the kwargs include_start and include_end may be given to toggle the exclusivity of either end of the range. By default, include_start = True and include_end = False. If using the constructor Range(‘[2, 5)’), the type of bracket on either end indicates exclusivity - square bracket is inclusive and circle bracket is exclusive. This will take priority over the keyword arguments, if given.