使用幼稚和 tz 感知的日期时间实例测试相等性时出现意外行为

Unexpected behaviour when testing equality with naive and tz-aware datetime instances

提问人:user2246849 提问时间:4/30/2022 最后编辑:user2246849 更新时间:5/31/2022 访问量:233

问:

以下内容是在 Python 3.9.7 中生成的。

我很清楚 Python 中不允许在 tz 感知实例和幼稚实例之间进行比较,并引发 .但是,当测试相等性(使用 and 运算符)时,情况并非如此。事实上,比较总是返回:datetimeTypeError==!=False

import datetime
import pytz

t_tz_aware = datetime.datetime(2020, 5, 23, tzinfo=pytz.UTC)
t_naive = datetime.datetime(2020, 5, 23)

# Prints 'False'.
print(t_tz_aware == t_naive)

# Raises TypeError: can't compare offset-naive and offset-aware datetimes.
print(t_tz_aware < t_naive)

我检查了 datetime 库的源代码,用于比较 datetime 对象的函数有一个参数叫做(默认为):allow_mixedFalse

def _cmp(self, other, allow_mixed=False)

当设置为 时,使用运算符进行比较时,可以比较 tz 感知实例和朴素实例。否则,它会引发 TypeError:True==datetime

# When testing for equality, set allow_mixed to True.
# For all the other operators, it remains False.
def __eq__(self, other):
   if isinstance(other, datetime):
      return self._cmp(other, allow_mixed=True) == 0
if myoff is None or otoff is None:
   if allow_mixed:
      return 2 # arbitrary non-zero value
   else:
      raise TypeError("cannot compare naive and aware datetimes")

所以,这似乎是有意为之的行为。事实上,Pandas 对 comparison of 和 similar 的实现与此是一致的。pandas.Timestamps

我的问题是,原因是什么?我想,就像参数的名称所说的那样,通过这种方式,我们可以过滤包含朴素和 tz 感知实例(即“混合”)的对象集合。但这难道不会引入潜在错误和意外行为的来源吗?我错过了什么?datetime

deceze 的评论后编辑: 事实上,这仍然是“语义上正确的”(即,日期肯定是不同的)。

Python Pandas 日期时间 时区 比较

评论

0赞 deceze 4/30/2022
好吧,它当然可以告诉你它是不一样的(因为一个有时区,另一个没有)。它不能告诉你一个是在另一个之前还是之后,因为这个问题在天真和有意识的日期时间之间毫无意义。
0赞 user2246849 4/30/2022
@deceze没错,这是真的。我想知道除了它仍然“语义正确”之外,是否还有理由允许它。在我看来,测试幼稚和有意识的日期时间是否相等似乎不适用于明智的场景
0赞 deceze 4/30/2022
平等比较几乎从不会引起例外。您只想测试两个任意对象是否相等。除非他们明确是,否则简单的答案是“不”,而不是错误。大于/小于比较的情况并非如此,对于比较,对象需要明确可排序,并且“你的问题没有答案”(即错误)是可行的第三种结果。
0赞 user2246849 4/30/2022
@deceze这也是事实。此外,改变这一点也可能是非常不蟒蛇的。感谢您的讨论,起初我很困惑,但完全有道理。

答:

1赞 deceze 4/30/2022 #1

平等比较几乎从不会引起例外。你只想知道对象 A 是否等于对象 B,它有一个明确的答案。如果它们是相等的,因为这些对象定义了相等,那么答案是肯定的,在所有其他情况下都是否定的。天真的人与有意识的人不同,至少就一个有时区而另一个没有时区而言,所以它们显然是不平等的。datetime

但是,对于大于/小于的比较,对象显然需要是可排序的,否则无法回答问题。不能仅仅因为无法比较对象而返回比较,因为这意味着相反的比较应该返回,它也不能。因此,在这种情况下提出错误是正确的第三种可能结果。<False>=True

2赞 Joooeey 5/31/2022 #2

文档中可以看出,直到 Python 3.2 之前,在这些情况下实际上提出了 a:TypeError

在 3.3 版更改: 意识和幼稚之间的平等比较 datetime 实例不会引发 TypeError。

2012 年,Python 开发人员考虑了这两个问题之间的权衡:

  • 提高一个可以更容易地捕捉由混合幼稚和有意识对象的严重错误引起的错误。TypeErrordatetime
  • 在 Python 中,您几乎可以对任何对象组合使用相等性比较。仅为 object 提出 a 会破坏这种一致性。TypeErrordatetime

以下是 Python 开发人员邮件列表上的相关讨论:

当您的日期时间对象是新创建的时,这很好。事实并非如此 当其中一些已经存在时,例如在数据库中(使用 ORM) 层)。混合幼稚和有意识的日期时间目前是一场灾难,因为 甚至基本操作(如相等比较)也会因 TypeError (它 一定是 stdlib 中唯一具有这种毒性的类型 行为)。

比较感知和幼稚的日期时间对象没有多大意义,但是 这是一个容易犯的错误。我会说 TypeError 是明智的 警告您的方式,而简单地返回 False 可能会导致很多 混乱。

你可以对同样“令人困惑”的结果说同样的话,但平等从来都不是 引发 TypeError(日期时间实例之间除外):

>>> () == []
False

提出异常具有非常严重的影响,例如使例外 不可能将这些对象放在同一个字典中。

甚至离家更近,

>>> date(2012,6,1) == datetime(2012,6,1)
`False`

我同意,平等比较不应该引起例外。

让我们这样吧。

-- --Guido van Rossum (python.org/~guido)

看起来在这次交流中,删除异常的论据更强。Guido van Rossum 是 Python 语言的创造者,对此类问题拥有最终决定权。这就是为什么他曾经被称为终生的仁慈者。因此,在他的“让我们让它如此”之后,行为发生了变化,因此天真和有意识的对象总是比较不相等,而不是提出一个.datetimeTypeError

评论

0赞 user2246849 5/31/2022
谢谢。这个答案实际上显示了我自己在思考这个问题时遇到的同样的“困境”。我试图简要地找到有关最终决定的背景,但找不到。看到正在讨论的推理和权衡真的很有趣。最后,我应该说我同意这个决定。我会将这个标记为公认的答案,因为它有更多我正在寻找的上下文。