Python两个对象相等的原理

本文详细介绍了Python中对象相等性和Hash值的概念及其实现方式,包括__eq__和__hash__方法的作用,以及如何正确实现这些方法以避免出现错误。

概述

  大部分的python程序员平时编程的时候,很少关心两个对象为什么相等,因为教程和经验来说,他们就应该相等,比如1==1就应该返回True,可是当我们想要定义自己的对象或者修改默认的对象行为时,通常会因为不了解原理而导致各种奇奇怪怪的错误。

两个对象如何相等

  两个对象如何才能相等要比我们想象的复杂很多,但核心的方法是重写__eq__方法,这个方法返回True,则表示两个对象相等,否则,就不相等。相反的,如果两个对象不相等,则重写__ne__方法。
  默认情况下,如果你没有实现这个方法,则使用父类(object)的方法。父类的方法比较是的两个对象的ID(可以通过id方法获取对象ID),也就是说,如果对象的ID相等,则两个对象也就相等。因此,我们可以得知,默认情况下,对象只和自己相等。例如:


>>> class A(object):
...     pass
...
>>>
>>> a = A()
>>> b = A()
>>> a == a
True
>>> a == b
False
>>> id(a)
4343310992
>>> id(b)
4343310928

  Python2程序员经常犯的一个错误是,只重写了__eq__方法,而没有重写__ne__方法,导致不可预计的错误。而Python3会自动重写__ne__方法,如果你没有重写的话。

对象的Hash方法

  Python里可Hash的对象,都有一个数字ID代表了它在python里的值,这个ID是由对象的__hash__方法返回的。因此,如果想让一个对象可Hash,那必须实现__hash__方法和之前提到的__eq__方法。和对象相等一样,默认情况下,对象的__hash__方法继承自Object对象,而Object对象的__hash__方法只计算对象ID,因此两个对象始终拥有两个不一样的hash id,不管他们是多么相似。
  当我们把一个不可Hash的对象加入到set或者dict时,会发生什么了?

>>> set().add({})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'
unhashable type: 'dict'

原因是set()和dict()使用对象的hash值作为内部索引,以便能快速索引到指定对象。因此,同一个对象返回相同的hash id就很重要了。

对象的Hash值在它的生命周期内不能改变

  如果你想定义一个比较完美的对象,并且实现了__eq__和__hash__方法来定义对象的比较行为和hash值,那么你就需要保证对象的相关属性不能发生更改。不然会导致很诡异的错误,比如下面的例子。

>>> class C:
...     def __init__(self, x):
...         self.x = x
...     def __repr__(self):
...         return "C({"+str(self.x)+"})"
...     def __hash__(self):
...         return hash(self.x)
...     def __eq__(self, other):
...         return (
...             self.__class__ == other.__class__ and
...             self.x == other.x
...         )
>>> d = dict()
>>> s = set()
>>> c = C(1)
>>> d[c] = 42
>>> s.add(c)
>>> d, s
({C(1): 42}, {C(1)})
>>> c in s and c in d  # c is in both!
True
>>> c.x = 2
>>> c in s or c in d   # c is in neither!?
False
>>> d, s
({C(2): 42}, {C(2)})   # but...it's right there!

在我们没有修改对象的属性时(c.x=2)之前,所有行为都符合预期。当我们通过c.x=2时修改属性后,执行c in s or c in d返回False,但是内容却是修改后的,是不是很奇怪。这也就解释了为什么str、tuple是可Hash的,而list和dict是不可hash的。

因此我们可以得出结论,如果两个对象相等的话,那它们的hash值必然也是相等的。

总结

讲了这么多有什么用了。

  1. 当我们遇到unhashable type这个异常时,我们能够知道为什么报这个错误。
  2. 如果定义了一个可比较的对象,那么最好保证对象hash值相关的属性在生命周期内不能发生改变,不然会发生意想不到的错误。

转载自我的博客捕蛇者说

<think> 首先,用户的问题是:“我想查找使用Python比较两个时间点大小的方法 请问Python中比较两个时间点大小的方法”。这是在询问如何在Python中比较两个时间点的大小,比如判断哪个时间更早或更晚。 根据系统级指令: - 所有行内数学表达式必须使用$...$格式。 - 独立公式使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识。 - 回答结构清晰,帮助用户逐步解决问题。 用户上一次的问题是询问Python比较时间点大小的方法,所以我需要直接回答这个。 关键点: - 在Python中,处理时间通常使用datetime模块。 - 比较时间点的大小可以通过比较datetime对象来实现。 - 例如,比较两个datetime对象:如果datetime1 < datetime2,则表示datetime1在datetime2之前。 步骤: 1. **引入datetime模块**:首先需要导入datetime类。 2. **创建时间对象**:使用datetime类来表示时间点。 3. **比较操作**:可以直接使用比较运算符如<, >, ==等。 4. **示例代码**:提供一个简单的代码示例来演示。 在回答中,我需要使用中文,并且生成相关问题。 结构建议: - 先简要解释方法。 - 然后提供代码示例。 - 最后生成相关问题。 确保LaTeX语法正确:在这个上下文中,可能没有数学表达式,但如果涉及到时间计算,可能需要使用。用户的问题是关于比较时间点,可能不需要复杂的数学公式。但如果有涉及,比如时间差,我需要使用$...$。 例如,如果提到时间差,可以说:时间差可以用$\Delta t$表示。 但在这个回答中,焦点是比较,而不是计算差。 生成相关问题:指令说“生成相关问题”,意思是附加一些问题以帮助用户深入学习。在示例中,它是“§§相关问题§§”,然后列出几个问题。 所以,在回答末尾,我需要添加: §§相关问题§§ 1. ... 2. ... 3. ... 引用标识:指令说“回答中引用的段落末尾自然地添加引用标识”。但用户提到了“参考站内引用”,但说“上述引用仅供参考,并不是用户真正的需求”,所以我应该忽略那个,专注于用户的问题。可能没有外部引用来源,所以我需要确保如果有引用,添加标识,但这里可能不需要专门的引用。 用户消息的开头是“参考站内引用:。上述引用仅供参考...”,但这似乎是系统消息的一部分。用户说“我想查找...”,所以我直接回答。 最终回答结构: 1. **引言**:简要解释Python中比较时间点的方法。 2. **方法步骤**:分步骤说明如何做。 3. **代码示例**:提供Python代码示例。 4. **结论**:总结。 5. **相关问题**:生成几个相关问题。 代码示例:使用代码块,就像示例中的Python代码一样。 在回答中,使用中文。 生成的相关问题:应该与主题相关,比如: - 如何处理时区? - 如何计算时间差? - datetime模块的其他功能? 确保回答真实可靠。 </think> ### Python中比较两个时间点大小的方法 在Python中,比较两个时间点的大小(判断哪个时间更早或更晚)非常简单且高效。主要通过`datetime`模块来实现。`datetime`模块提供了`datetime`类来表示日期和时间点,这些对象支持直接的比较运算符(如 `<`, `>`, `==`),类似于比较数字的大小。以下我将逐步解释方法,并提供代码示例。 #### 步骤说明 1. **导入datetime模块**:首先,需要从Python标准库中导入`datetime`类。 2. **创建时间点对象**:使用`datetime`类创建两个时间对象。每个对象需要指定年、月、日、时、分、秒等参数。 3. **使用比较运算符**:直接使用Python的比较运算符(如 `<` 表示早于, `>` 表示晚于, `==` 表示相等)来比较两个对象。 4. **处理结果**:比较结果返回布尔值(`True`或`False`),可用于条件判断或进一步操作。 #### 代码示例 以下是一个完整的Python代码示例,演示如何比较两个时间点的大小: ```python from datetime import datetime # 创建两个时间点对象:time1 和 time2 time1 = datetime(2023, 10, 1, 14, 30, 0) # 2023年10月1日 14:30:00 time2 = datetime(2023, 10, 1, 16, 45, 0) # 2023年10月1日 16:45:00 # 比较时间点大小 if time1 < time2: print(f"time1 早于 time2") # time1 在 time2 之前 elif time1 > time2: print(f"time1 晚于 time2") # time1 在 time2 之后 else: print(f"time1 等于 time2") # 时间点相同 # 输出:time1 早于 time2 ``` #### 关键点说明 - **为什么使用datetime模块?** `datetime`类是Python标准库的一部分,专为处理日期和时间设计,支持精确到微秒的比较,且无需额外安装。 - **时间对象的创建**:可以通过`datetime(year, month, day, hour, minute, second)`指定具体时间点。如果需要处理当前时间,可以使用`datetime.now()`。 - **比较原理**:Python内部将时间点转换为时间戳(自1970年1月1日以来的秒数),然后进行比较。因此,比较操作的时间复杂度为$O(1)$,非常高效[^1]。 - **注意事项**: - 确保比较的两个时间对象都是`datetime`类型,否则会引发错误(如比较字符串时间)。 - 如果时间涉及不同时区,需使用`pytz`库处理时区转换,避免比较错误。 这个方法在实际项目中广泛应用,例如在日志分析、任务调度或数据处理中比较事件发生的先后顺序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值