【问题标题】:Python using a datetime slice as a dictonary keyPython 使用日期时间片作为字典键
【发布时间】:2014-12-02 16:54:52
【问题描述】:

是否可以使用 datetime slice 作为字典键?

例子:

st = datetime.datetime(2010, 12, 31, 0, 0)
en = datetime.datetime(2011, 12, 28, 0, 0)
b = [] 
b[slice(st,en)] = 'some_random_value_could_be_anything' # Error!

发生两个错误之一:

在单个字典的情况下:

TypeError: slice indices must be integers or None or have an __index__ method

在嵌套字典的情况下:

TypeError: unhashable type

【问题讨论】:

  • 你想通过切片日期时间来实现什么?
  • 这不会有效。因为对于切片,您的列表需要一个整数索引值(绝对可排序)。它必须是最小值,在您的情况下为分钟。一年内,您将获得超过 50 万个条目。告诉我们更多关于问题本身的信息。
  • @wenzul:实际上,slice() 可以使用任意对象。只要您将这些传递给理解datetime 对象的__getitem__ 方法就可以了。 slice() 对象它们自己 不关心类型。这使您可以执行some_datetime_sequence[starting_datetime:ending_datetime],Python 将处理它作为some_datetime_sequence.__getitem__(slice(starting_datetime, ending_datetime))
  • 分配100 想要达到什么目的?那不应该是一个数组吗?
  • 有趣的问题。也许你想看看这个:stackoverflow.com/questions/2936863/…?我自己也在玩这个:-)

标签: python dictionary python-datetime


【解决方案1】:

免责声明:这个答案主要是为了说明 Python 允许您做的一件(众多)事情。此外,您可能应该想以这种方式做事(您可以做事并不意味着您应该

这就是说,getitem(self, key) 的文档指出:

对于序列类型,接受的键应该是整数和切片 对象。

这意味着任何想要模仿 sequence type 行为的类(例如列表)都必须准备好实现__getitem__,其中键可以是slice 类型.这(某种)解释了(或至少在某种程度上与)为什么在你的字典中当你尝试做b[slice(st,en)] = 'bla ' 时你得到一个TypeError: unhashable type:这是因为它试图使用slice(st, en) 实例作为字典键。 slice 对象不可散列,因此不能用作 dict 键。 dict 类型不是序列类型,因此尝试对字典进行切片没有任何意义。

假设你有:

{ ...“富”:1, ...“酒吧”:2, ...“巴兹”:3, ... }

'foo''bar'slice 是什么意思?你会按照你输入的顺序返回这组键吗? ('foo','bar','baz')? Python 不知道这一点。会是他们的__hash__吗?这是内部的,在切片时毫无意义。

说了这么多,这是一件非常非常糟糕的事情……但这“有效”:

import datetime


class DatetimeDict(dict):
    def __getitem__(self, key):
        if isinstance(key, slice):
            sliced = {}
            start_dt = key.start
            stop_dt = key.stop
            step = key.step or 1
            internal_keys = sorted(self.keys())
            if start_dt is None:
                start_index = 0
            else:
                start_index = internal_keys.index(start_dt)
            end_index = internal_keys.index(stop_dt)
            for i in range(start_index, end_index, step):
                sliced.update({internal_keys[i]: self[internal_keys[i]]})
            return sliced
        else:
            return super(DatetimeDict, self).__getitem__(key)

    def __setitem__(self, key, val):
        return super(DatetimeDict, self).__setitem__(key, val)

a = DatetimeDict()
a[datetime.datetime.strptime('2014/01/01', '%Y/%m/%d')] = 'foo',
a[datetime.datetime.strptime('2014/01/02', '%Y/%m/%d')] = 'bar',
a[datetime.datetime.strptime('2014/01/03', '%Y/%m/%d')] = 'baz',
a[datetime.datetime.strptime('2014/01/04', '%Y/%m/%d')] = 'bla',

from_dt = datetime.datetime.strptime('2014/01/02', '%Y/%m/%d')
to_dt = datetime.datetime.strptime('2014/01/04', '%Y/%m/%d')
print a[from_dt:to_dt]

这个输出:

{
    datetime.datetime(2014, 1, 2, 0, 0): ('bar',), 
    datetime.datetime(2014, 1, 3, 0, 0): ('baz',)
}

但是很糟糕,很糟糕......DatetimeDict 变成了一个 奇怪 结构,它是一个字典,但同时表现得有点像序列类型...... 糟糕。

编辑(重新阅读后,我很确定我误解了这个问题)

你实际上并没有试图分割dict,你在哪里?当我学会阅读的那一天,我将征服世界...... :-D

如果您想要使用一系列日期时间作为dict 键,我建议您只需将startend 放在tuple 中:

>>> import datetime
>>> st = datetime.datetime.strptime('2014/01/01', '%Y/%m/%d')
>>> en = datetime.datetime.strptime('2014/01/02', '%Y/%m/%d')
>>> key = (st, en)
>>> a = {}
>>> a[key] = 'foo'
>>> print a
{(datetime.datetime(2014, 1, 1, 0, 0), datetime.datetime(2014, 1, 2, 0, 0)): 'foo'}

Weeeelllll...至少我学会了切片之类的东西...大声笑...

【讨论】:

  • 别自责,问题本身就很不清楚。附言“你做事并不意味着你应该”——模仿是最真诚的奉承形式,所以谢谢你的赞美。也谢谢你教我一些关于切片的知识,我也不知道。
  • @BorrajaX 你第一次做对了!我的示例可能不清楚/具有误导性
  • @pyCthon 啊!!谢谢谢谢:-D 只是我花了... 1 小时来回,测试,yadda,yadda,然后我读了Python using a datetime slice as a dictonary key... 我想我自己,我把整个事情都搞反了......呸!!这是一种解脱!
【解决方案2】:

您可以从两个日期时间创建slice 对象。

你可以尝试用它来分割一个列表。但是,如果开始或结束是对象,则list 通过调用这些对象的__index__ 将这些对象解析为整数值,如here 所述。这是您遇到的第一个错误。

您遇到的第二个错误是因为您无法为slice 对象创建hash。 Python 总是为关键对象生成hash 值,因为字典类似于哈希列表。字典切片是不可能的,您可以使用一个辅助函数来模拟该行为。另一种方法是使用元组作为字典键,这样你就可以避免slice。元组是可散列的。

您可以尝试您建议的方法,为扩展的日期时间类实现 __index__ 方法。

class MyDateTime(datetime.datetime):
    def __index__(self):
        # in your case return number of minutes (in this case since 1970/1/1)
        return int((self-datetime.datetime(1970,1,1)).total_seconds()/60)

st = MyDateTime(2010, 12, 31, 0, 0)
en = MyDateTime(2011, 12, 28, 0, 0)

b = [] 
b[slice(st,en)] = [100]

在本例中,您将使用与分钟对应的最小公倍数。这将是最有用的,但更大的步骤也可能对您有用。

但我不建议用slice 和列表来实现一些日期时间间隔。我敢打赌,您会遇到其他问题,例如性能或复杂性。

您还没有添加您尝试解决的问题。我是对的,你想检查 DateTimeIntervals 是否有重叠等等。如果是,请查看DateTimeInterval

【讨论】:

    猜你喜欢
    • 2016-06-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-02
    • 2016-08-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多