我感兴趣的是在 Python 中表示一个范围,类似于 Guava 的
Range
类型。 具体来说,它应该有一个开始和结束,并表示两者之间的所有值(作为第一遍,我可以只表示规范的开闭范围,即 [5,10)
,但可以正确表示任何开/闭范围将是一个合理的特征)。
range()
内置,但我的目的是支持任意类型(或特别是日期,对于我的用例)。
看看Python的类型层次结构,从逻辑上看,范围似乎可以是
Sequence
或Set
类型,但我不确定哪个更有意义,是否最好放弃将我的类硬塞进去层次结构并简单地实现我想要的行为。
作为
Sequence
:
[0,+∞)
,所以以上可能不是真的。作为
Set
:
作为单独的结构:
range.slice()
方法)Range
并未从 Collection API 扩展这一事实似乎支持了这一论点。我很好奇这里最Pythonic的是什么,以及是否有人自己制作了任何这样的数据结构。
这是我迄今为止提出的实现。
Range
对象表示任意 openClosed 范围,并且是可散列、可包含和可迭代的,但既不是序列也不是集合。 DateRange
子类表示日期范围,这主要需要将增量参数定义为 timedelta(days=1)
而不是简单地 1
。
class Range:
'''
Represents a range, in the spirit of Guava's Range class.
Endpoints can be absent, and (presently) all ranges are openClosed.
There's little reason to use this class directly, as the range()
builtin provides this behavior for integers.
'''
def __init__(self, start, end, increment=1):
if start and end and end < start:
raise ValueError("End date cannot be before start date, %s:%s" % (start,end))
self.start = start
self.end = end
self.increment = increment
def __repr__(self):
return '[%s\u2025%s)' % (
self.start or '-\u221E',
self.end or '+\u221E'
)
def __eq__(self, other):
return self.start == other.start and self.end == other.end
def __hash__(self):
return 31*hash(self.start) + hash(self.end)
def __iter__(self):
cur = self.start
while cur < self.end:
yield cur
cur = cur + self.increment
def __contains__(self, elem):
ret = True
if self.start:
ret = ret and self.start <= elem
if self.end:
ret = ret and elem < self.end
return ret
class DateRange(Range):
'''A range of dates'''
one_day = timedelta(days=1)
@staticmethod
def parse(daterange):
'''Parses a string into a DateRange, useful for
parsing command line arguments and similar user input.
*Not* the inverse of str(range).'''
start, colon, end = daterange.partition(':')
if colon:
start = strToDate(start) if start else None
end = strToDate(end) if end else None
else:
start = strToDate(start)
end = start + DateRange.one_day
return DateRange(start, end)
def __init__(self, start, end):
Range.__init__(self, start, end, DateRange.one_day)
def strToDate(date_str):
'''Parses an ISO date string, such as 2014-2-20'''
return datetime.datetime.strptime(date_str, '%Y-%m-%d').date()
一些使用示例:
>>> DateRange(datetime.date(2014,2,20), None)
[2014-02-20‥+∞)
>>> DateRange(datetime.date(2014,1,1), datetime.date(2014,4,1))
[2014-01-01‥2014-04-01)
>>> DateRange.parse(':2014-2-20')
[-∞‥2014-02-20)
>>> DateRange.parse('2014-2-20:2014-3-22')
[2014-02-20‥2014-03-22)
>>> daterange = DateRange.parse('2014-2-20:2014-3-2')
>>> daterange
[2014-02-20‥2014-03-02)
>>> datetime.date(2014,1,25) in daterange
False
>>> datetime.date(2014,2,20) in daterange
True
>>> list(daterange)
[datetime.date(2014, 2, 20), datetime.date(2014, 2, 21), datetime.date(2014, 2, 22),
datetime.date(2014, 2, 23), datetime.date(2014, 2, 24), datetime.date(2014, 2, 25),
datetime.date(2014, 2, 26), datetime.date(2014, 2, 27), datetime.date(2014, 2, 28),
datetime.date(2014, 3, 1)]
pd.Interval是您要找的吗?
数字和日期时间快速演示:
import pandas as pd
import numpy as np
interval_0_1 = pd.Interval(left=0, right=1, closed='right')
print(interval_0_1)
vals = np.linspace(-.5,1.5,5)
pd.DataFrame({'value':vals, 'in interval ' + str(interval_0_1):[val in interval_0_1 for val in vals]})
interval_2000 = pd.Interval(left=pd.to_datetime('01-01-2000'), right=pd.to_datetime('01-01-2001'), closed='left')
print(interval_2000)
vals = pd.date_range(start=pd.to_datetime('01-01-2000'), end=pd.to_datetime('01-01-2001'), periods=3)
pd.DataFrame({'value':vals, 'in interval ' + str(interval_2000):[val in interval_2000 for val in vals]})