Python,我应该基于__eq__实现__ne __()运算符吗?

时间:2010-12-04 06:18:35

标签: python comparison operators python-datamodel

我有一个课我要覆盖__eq__()运算符。似乎我应该覆盖__ne__()运算符,但基于__ne__实现__eq__是否有意义呢?

class A:
    def __eq__(self, other):
        return self.value == other.value

    def __ne__(self, other):
        return not self.__eq__(other)

或者,Python使用这些运算符的方式是否缺少某些内容,这使得这不是一个好主意?

5 个答案:

答案 0 :(得分:93)

  

Python,我应该基于__ne__()实现__eq__运算符吗?

简答:否。使用==代替__eq__

在Python 3中,默认情况下!===的否定,因此您甚至不需要编写__ne__,并且文档不再适用于编写文档。

一般来说,对于仅限Python 3的代码,除非您需要掩盖父实现,例如,否则不要编写代码。对于内置对象。

也就是说,请记住Raymond Hettinger's comment

  

__ne__方法仅在__eq__时自动跟随   __ne__尚未在超类中定义。所以,如果你是   从内置继承,最好覆盖它们。

如果您需要在Python 2中使用您的代码,请遵循Python 2的建议,它将在Python 3中正常工作。

在Python 2中,Python本身不会自动实现任何操作 - 因此,您应该使用__ne__而不是==来定义__eq__。 例如。

class A(object):
    def __eq__(self, other):
        return self.value == other.value

    def __ne__(self, other):
        return not self == other # NOT `return not self.__eq__(other)`

见证明

  • 根据__ne__()
  • 实施__eq__运算符
  • 根本没有在Python 2中实现__ne__

在下面的演示中提供了错误的行为。

长答案

Python 2的documentation说:

  

比较运营商之间没有隐含的关系。该   x==y的真相并不意味着x!=y是假的。因此,何时   定义__eq__(),还应定义__ne__()以便{。}}   运营商将按预期行事。

这意味着如果我们根据__ne__的倒数定义__eq__,我们就可以获得一致的行为。

本文档的这一部分已针对Python 3:

进行了更新
  

默认情况下,__ne__()委托给__eq__()并反转结果   除非它是NotImplemented

"what's new" section中,我们看到此行为已更改:

  
      
  • !=现在返回==的反面,除非==返回NotImplemented
  •   

为了实施__ne__,我们更倾向于使用==运算符,而不是直接使用__eq__方法,以便在self.__eq__(other)时使用NotImplemented子类为选中的类型返回other.__eq__(self),Python将适当地检查NotImplemented From the documentation

NotImplemented对象

  

此类型具有单个值。有一个具有此值的对象。可以通过内置名称访问此对象   other。数值方法和丰富的比较方法可能会返回   如果它们没有实现操作数的操作,则为该值   提供。 (解释器然后将尝试反射操作,或   其他一些后备,取决于运算符。)它的真值是   真。

当给定一个富比较运算符时,如果它们不是同一类型,Python会检查other是否为子类型,如果它定义了该运算符,则使用<首先是方法(与<=>=>NotImplemented相反)。如果返回==,则然后使用相反的方法。 (它检查两次相同的方法。)使用__ne__运算符可以实现此逻辑。

期望

从语义上讲,您应该在检查相等性方面实现def negation_of_equals(inst1, inst2): """always should return same as not_equals(inst1, inst2)""" return not inst1 == inst2 def not_equals(inst1, inst2): """always should return same as negation_of_equals(inst1, inst2)""" return inst1 != inst2 ,因为您的类的用户希望以下函数对于A的所有实例都是等效的。

__ne__

也就是说,上述两个函数都应始终返回相同的结果。但这取决于程序员。

基于__eq__定义class BaseEquatable(object): def __init__(self, x): self.x = x def __eq__(self, other): return isinstance(other, BaseEquatable) and self.x == other.x class ComparableWrong(BaseEquatable): def __ne__(self, other): return not self.__eq__(other) class ComparableRight(BaseEquatable): def __ne__(self, other): return not self == other class EqMixin(object): def __eq__(self, other): """override Base __eq__ & bounce to other for __eq__, e.g. if issubclass(type(self), type(other)): # True in this example """ return NotImplemented class ChildComparableWrong(EqMixin, ComparableWrong): """__ne__ the wrong way (__eq__ directly)""" class ChildComparableRight(EqMixin, ComparableRight): """__ne__ the right way (uses ==)""" class ChildComparablePy3(EqMixin, BaseEquatable): """No __ne__, only right in Python 3.""" 时出现意外行为:

首先是设置:

right1, right2 = ComparableRight(1), ChildComparableRight(2)
wrong1, wrong2 = ComparableWrong(1), ChildComparableWrong(2)
right_py3_1, right_py3_2 = BaseEquatable(1), ChildComparablePy3(2)

实例化非等效实例:

__ne__

预期行为:

(注意:虽然下面每一个的每一个断言都是等价的,因此在逻辑上多余于它之前的断言,我将包括它们来证明顺序无关紧要当一个是子类时另一个。

这些实例==实施了assert not right1 == right2 assert not right2 == right1 assert right1 != right2 assert right2 != right1

assert not right_py3_1 == right_py3_2
assert not right_py3_2 == right_py3_1
assert right_py3_1 != right_py3_2
assert right_py3_2 != right_py3_1

这些在Python 3下测试的实例也能正常工作:

__ne__

回想一下,__eq__实现了assert not wrong1 == wrong2 # These are contradicted by the assert not wrong2 == wrong1 # below unexpected behavior! - 虽然这是预期的行为,但实施不正确:

not wrong1 == wrong2

意外行为:

请注意,此比较与上述比较(>>> assert wrong1 != wrong2 Traceback (most recent call last): File "<stdin>", line 1, in <module> AssertionError )相矛盾。

>>> assert wrong2 != wrong1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError

__ne__

不要在Python 2中跳过__ne__

有关您不应在Python 2中跳过实现>>> right_py3_1, right_py3_1child = BaseEquatable(1), ChildComparablePy3(1) >>> right_py3_1 != right_py3_1child # as evaluated in Python 2! True 的证据,请参阅以下等效对象:

False

以上结果应为__ne__

Python 3源

case Py_NE: /* By default, __ne__() delegates to __eq__() and inverts the result, unless the latter returns NotImplemented. */ if (self->ob_type->tp_richcompare == NULL) { res = Py_NotImplemented; Py_INCREF(res); break; } res = (*self->ob_type->tp_richcompare)(self, other, Py_EQ); if (res != NULL && res != Py_NotImplemented) { int ok = PyObject_IsTrue(res); Py_DECREF(res); if (ok < 0) res = NULL; else { if (ok) res = Py_False; else res = Py_True; Py_INCREF(res); } } 的默认CPython实现位于typeobject.c in object_richcompare

__ne__

我们在这里看到

但默认__eq__使用__ne__

Python 3的C级默认__eq__实施详细信息使用==,因为较高级别NotImplementedPyObject_RichCompare)效率较低 - 因此它还必须处理__eq__

如果==被正确实施,那么__ne__的否定也是正确的 - 它允许我们在==中避免低级别的实施细节。

使用NotImplemented可以让我们将低级别逻辑保留在一个位置,避免<{em>} __ne__中的==

有人可能错误地认为NotImplemented可能会返回__eq__

它实际上使用与class Foo: def __ne__(self, other): return NotImplemented __eq__ = __ne__ f = Foo() f2 = Foo() 的默认实现相同的逻辑,用于检查身份(请参阅下面的do_richcompare和我们的证据)

>>> f == f
True
>>> f != f
False
>>> f2 == f
False
>>> f2 != f
True

比较:

class CLevel:
    "Use default logic programmed in C"

class HighLevelPython:
    def __ne__(self, other):
        return not self == other

class LowLevelPython:
    def __ne__(self, other):
        equal = self.__eq__(other)
        if equal is NotImplemented:
            return NotImplemented
        return not equal

def c_level():
    cl = CLevel()
    return lambda: cl != cl

def high_level_python():
    hlp = HighLevelPython()
    return lambda: hlp != hlp

def low_level_python():
    llp = LowLevelPython()
    return lambda: llp != llp

效果

不要接受我的话,让我们看看它的性能更高:

>>> import timeit
>>> min(timeit.repeat(c_level()))
0.09377292497083545
>>> min(timeit.repeat(high_level_python()))
0.2654011140111834
>>> min(timeit.repeat(low_level_python()))
0.3378178110579029

我认为这些表现数字不言自明:

low_level_python

当您考虑not self == other在Python中执行逻辑时,这是有道理的,否则将在C级别处理。

对一些评论家的回应

另一位回答者写道:

  

Aaron Hall对__ne__方法的实施NotImplemented不正确,因为它永远不会返回not NotImplementedFalse__ne__),因此{{1具有优先级的方法永远不会回退到没有优先级的__ne__方法。

__ne__永不返回NotImplemented并不会使其不正确。相反,我们通过检查与NotImplemented的相等性来处理==的优先级。假设==已正确实施,我们已完成。

  

not self == other曾经是__ne__方法的默认Python 3实现,但它是一个错误,它在2015年1月的Python 3.4中得到了纠正,正如ShadowRanger注意到的那样(参见问题#21408)。

好吧,让我们来解释一下。

如前所述,Python 3默认处理__ne__,首先检查self.__eq__(other)是否返回NotImplemented(单例) - 应该使用is检查并返回如果是这样,否则它应该返回逆。以下是写为mixin的逻辑:

class CStyle__ne__:
    """Mixin that provides __ne__ functionality equivalent to 
    the builtin functionality
    """
    def __ne__(self, other):
        equal = self.__eq__(other)
        if equal is NotImplemented:
            return NotImplemented
        return not equal

这对于C级Python API的正确性是必要的,它是在Python 3中引入的,正在制作

多余的。已删除所有相关的__ne__方法,包括实施自己的检查以及直接或__eq__委托给==的方法 - 而==是最常见的方法这样做。

结论

对于Python 2兼容代码,请使用==来实现__ne__。它更多:

  • 正确
  • 简单
  • 高性能

仅在Python 3中,使用C级别的低级否定 - 它甚至更多简单且高效(尽管程序员负责确定它正确)。

同样,在高级Python中编写低级逻辑。

答案 1 :(得分:47)

是的,这完全没问题。事实上,the documentation会促使您在定义__ne__时定义__eq__

  

没有隐含的关系   在比较运算符中。该   x==y的真相并不意味着x!=y   是假的。因此,在定义时   __eq__(),还应该定义__ne__(),以便运算符按预期运行。

在很多情况下(比如这个),它就像否定__eq__的结果一样简单,但并非总是如此。

答案 2 :(得分:7)

只是为了记录,规范正确并且交叉Py2 / Py3便携式__ne__看起来像:

import sys

class ...:
    ...
    def __eq__(self, other):
        ...

    if sys.version_info[0] == 2:
        def __ne__(self, other):
            equal = self.__eq__(other)
            return equal if equal is NotImplemented else not equal

这适用于您可能定义的任何__eq__

  • not (self == other)不同,在涉及比较的一些烦人/复杂案件中不会干扰,其中一个类别并不意味着__ne__的结果与not __eq__的结果(例如SQLAlchemy的ORM,其中__eq____ne__都返回特殊代理对象,而不是True或{{ 1}},并尝试False not的结果将返回__eq__,而不是正确的代理对象)。
  • False不同,当not self.__eq__(other)返回__ne__时,这会正确委托给其他实例的self.__eq__NotImplemented会出错,因为{{ 1}}是真实的,所以当not self.__eq__(other)不知道如何进行比较时,NotImplemented会返回__eq__,这意味着这两个对象实际上是唯一的被问到的对象不知道,这意味着默认不等于)

如果您的__ne__没有使用False返回,则此功能(无意义的开销),如果有时使用__eq__,则会正确处理。 Python版本检查意味着如果类是NotImplemented - 在Python 3中编辑,则NotImplemented未定义,允许Python的本机高效回退__ne__ implementation (a C version of the above)接管。

为什么需要

Python重载规则

为什么你这样做而不是其他解决方案的解释有些神秘。 Python有一些关于重载运算符的一般规则,特别是比较运算符:

  1. (适用于所有运营商)运行import时,请尝试__ne__,如果返回LHS OP RHS,请尝试LHS.__op__(RHS)。例外:如果NotImplementedRHS.__rop__(LHS)类的子类,则先测试RHS 。对于比较运算符,LHSRHS.__rop__(LHS)是他们自己的&#34; rop&#34;因此__eq__的测试顺序为__ne__,然后{ {1}},如果__ne__LHS.__ne__(RHS)类的子类,则撤消
  2. 除了&#34;交换&#34;的想法运营商,运营商之间没有隐含的关系。即使是同一个类,RHS.__ne__(LHS)返回RHS并不意味着LHS返回LHS.__eq__(RHS)(事实上,运算符甚至不需要返回布尔值;像SQLAlchemy这样的ORM故意不这样做,允许更具表现力的查询语法)。从Python 3开始,默认的True实现就是这样的,但它不是契约性的;您可以以LHS.__ne__(RHS)的严格对立方式覆盖False
  3. 这如何适用于重载比较器

    因此,当您重载运算符时,您有两个作业:

    1. 如果您自己知道如何实施操作,请使用 自己知道如何进行比较(从不隐式或明确地委托给操作的另一方;这样做会冒不正确和/或无限递归的风险,具体取决于你的工作方式)
    2. 如果您知道如何自己实施该操作,总是返回__ne__,因此Python可以委托给另一个操作数&#39 ; s实施
    3. __ne__

      的问题
      __eq__

      从不委托给另一方(如果NotImplemented正确返回not self.__eq__(other),则不正确)。当def __ne__(self, other): return not self.__eq__(other) 返回__eq__(&#34;真实&#34;)时,您会默默地返回NotImplemented,因此self.__eq__(other)会返回NotImplemented,检查了False是否知道如何与A() != something_A_knows_nothing_about的实例进行比较,如果没有,则应该返回False(因为如果双方都不知道如何与另外,他们被认为彼此不相等)。如果something_A_knows_nothing_about未正确实施(当它不识别另一方时,则返回A而不是True),那么这是&#34;正确的&#34;从A.__eq__的角度来看,返回False(因为NotImplemented并不认为它是平等的,所以它不相等),但是从A的角度来看可能是错的,因为它甚至从未问过True; A结束something_A_knows_nothing_about,但something_A_knows_nothing_about可以A() != something_A_knows_nothing_about,或任何其他返回值。

      True

      的问题
      something_A_knows_nothing_about != A()

      更微妙。 99%的课程都是正确的,包括Falsenot self == other的逻辑反转的所有课程。但def __ne__(self, other): return not self == other 违反了上述两条规则,这意味着对于__ne__ 不是__eq__的逻辑反转的类,结果是一次再次非反身,因为其中一个操作数永远不会被问及是否可以实现not self == other,即使其他操作数不能。最简单的示例是一个weirdo类,它为所有比较返回__ne__,因此__eq____ne__都返回False。正确实现A() == Incomparable()(当它不知道如何进行比较时返回A() != Incomparable()),这种关系是反身的; FalseA.__ne__就结果达成一致(因为在前一种情况下,NotImplemented会返回A() != Incomparable(),然后Incomparable() != A()会返回A.__ne__,而在后者,NotImplemented直接返回Incomparable.__ne__。但是当False实施为Incomparable.__ne__时,False会返回A.__ne__(因为return not self == other返回,而不是A() != Incomparable(),然后True会返回A.__eq__NotImplementedIncomparable.__eq__转换为False,而A.__ne__返回True

      您可以在行动here中看到此示例。

      显然,一个总是为Incomparable() != A()False.返回False的类有点奇怪。但如前所述,__eq____ne__甚至不需要返回__eq__ / __ne__; SQLAlchemy ORM具有带有比较器的类,它们返回一个用于查询构建的特殊代理对象,而不是True / False(如果在布尔值中进行评估,它们就是&#34; truthy&#34;上下文,但他们永远不应该在这样的背景下进行评估。)

      由于未能正确地重载True破坏该类的类,如代码:

      False

      将起作用(假设SQLAlchemy知道如何将__ne__插入到SQL字符串中;这可以通过类型适配器来完成,而 results = session.query(MyTable).filter(MyTable.fieldname != MyClassWithBadNE()) 根本不需要合作),将预期的代理对象传递给MyClassWithBadNE,同时:

      MyClassWithBadNE

      将最终传递filter一个普通 results = session.query(MyTable).filter(MyClassWithBadNE() != MyTable.fieldname) ,因为filter会返回一个代理对象,False只会将真实的代理对象转换为self == other。希望not self == other在处理False之类的无效参数时抛出异常。虽然我确信很多人会认为filter 应该始终位于比较的左侧,但事实仍然是没有程序化的理由来强制执行此操作。大小写,正确的通用False将以任何方式工作,而MyTable.fieldname仅适用于一种方式。

答案 3 :(得分:2)

简短回答:是(但请阅读文档以正确执行)

ShadowRanger对__ne__方法的实现是正确的(在某种意义上它的行为与默认的Python 3实现完全相同):

def __ne__(self, other):
    result = self.__eq__(other)

    if result is not NotImplemented:
        return not result

    return NotImplemented

Aaron Hall对not self == other方法的实施__ne__不正确,因为它永远不会返回NotImplementednot NotImplementedFalse),因此{{1具有优先级的方法永远不会回退到没有优先级的__ne__方法。 __ne__曾经是not self == other方法的默认Python 3实现,但它是一个错误,并且在2015年1月的Python 3.4中得到了纠正,正如ShadowRanger注意到的那样(请参阅issue #21408)。 / p>

比较运算符的实现

用于Python 3的 Python语言参考chapter III Data model中说明:

  

__ne__
  object.__lt__(self, other)
  object.__le__(self, other)
  object.__eq__(self, other)
  object.__ne__(self, other)
  object.__gt__(self, other)

     

这些是所谓的“丰富比较”方法。通信   运算符符号和方法名称之间如下:object.__ge__(self, other)调用   x<yx.__lt__(y)来电x<=yx.__le__(y)来电x==y,   x.__eq__(y)来电x!=yx.__ne__(y)来电x>yx.__gt__(y)   致电x>=y

     

丰富的比较方法可能会返回单例x.__ge__(y) if   它没有实现给定参数对的操作。

     

这些方法没有交换参数版本(可以使用   当左参数不支持操作但右边   论证确实);相反,NotImplemented__lt__()是彼此的   反射,__gt__()__le__()是彼此的反映,和   __ge__()__eq__()是他们自己的反映。如果是操作数   是不同类型的,右操作数的类型是直接或   左操作数类型的间接子类,反射方法   右操作数具有优先级,否则为左操作数的方法   优先考虑。不考虑虚拟子类化。

将其翻译成Python代码(__ne__()使用operator_eq==使用operator_ne!=使用operator_lt,{{1} {}为<operator_gt>operator_le<=):

operator_ge

比较方法的默认实现

文档补充说:

  

默认情况下,>=委托给def operator_eq(left, right): if type(left) != type(right) and isinstance(right, type(left)): result = right.__eq__(left) if result is NotImplemented: result = left.__eq__(right) else: result = left.__eq__(right) if result is NotImplemented: result = right.__eq__(left) if result is NotImplemented: result = left is right return result def operator_ne(left, right): if type(left) != type(right) and isinstance(right, type(left)): result = right.__ne__(left) if result is NotImplemented: result = left.__ne__(right) else: result = left.__ne__(right) if result is NotImplemented: result = right.__ne__(left) if result is NotImplemented: result = left is not right return result def operator_lt(left, right): if type(left) != type(right) and isinstance(right, type(left)): result = right.__gt__(left) if result is NotImplemented: result = left.__lt__(right) else: result = left.__lt__(right) if result is NotImplemented: result = right.__gt__(left) if result is NotImplemented: raise TypeError(f"'<' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'") return result def operator_gt(left, right): if type(left) != type(right) and isinstance(right, type(left)): result = right.__lt__(left) if result is NotImplemented: result = left.__gt__(right) else: result = left.__gt__(right) if result is NotImplemented: result = right.__lt__(left) if result is NotImplemented: raise TypeError(f"'>' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'") return result def operator_le(left, right): if type(left) != type(right) and isinstance(right, type(left)): result = right.__ge__(left) if result is NotImplemented: result = left.__le__(right) else: result = left.__le__(right) if result is NotImplemented: result = right.__ge__(left) if result is NotImplemented: raise TypeError(f"'<=' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'") return result def operator_ge(left, right): if type(left) != type(right) and isinstance(right, type(left)): result = right.__le__(left) if result is NotImplemented: result = left.__ge__(right) else: result = left.__ge__(right) if result is NotImplemented: result = right.__le__(left) if result is NotImplemented: raise TypeError(f"'>=' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'") return result 并反转结果   除非是__ne__()。没有其他暗示   比较运算符之间的关系,例如,事实   __eq__()并不意味着NotImplemented

比较方法的默认实现((x<y or x==y)x<=y__eq____ne____lt____gt__)因此可以是由下式给出:

__le__

所以这是__ge__方法的正确实现。并且它并不总是返回def __eq__(self, other): return NotImplemented def __ne__(self, other): result = self.__eq__(other) if result is not NotImplemented: return not result return NotImplemented def __lt__(self, other): return NotImplemented def __gt__(self, other): return NotImplemented def __le__(self, other): return NotImplemented def __ge__(self, other): return NotImplemented 方法的倒数,因为当__ne__方法返回__eq__时,其倒数__eq__NotImplemented(因为{{1} }} not NotImplemented}而不是所需的False

bool(NotImplemented)

的实施不正确

正如Aaron Hall所示,True不是NotImplemented方法的默认实现。 但也不是__ne__下面通过比较两种情况下默认实现的行为与not self.__eq__(other)实现的行为来演示后者:

  • __ne__方法返回not self == other;
  • not self == other方法返回的值不同于__eq__

默认实施

让我们看看当NotImplemented方法使用默认实现并且__eq__方法返回NotImplemented时会发生什么:

A.__ne__
  1. A.__eq__来电NotImplemented
  2. class A: pass class B: def __ne__(self, other): return "B.__ne__" assert (A() != B()) == "B.__ne__" 来电!=
  3. A.__ne__返回A.__ne__
  4. A.__eq__来电A.__eq__
  5. NotImplemented返回!=
  6. 这表明当B.__ne__方法返回B.__ne__时,"B.__ne__"方法会回退到A.__eq__方法。

    现在让我们看看当NotImplemented方法使用默认实现并且A.__ne__方法返回的值不同于B.__ne__时会发生什么:

    A.__ne__
    1. A.__eq__来电NotImplemented
    2. class A: def __eq__(self, other): return True class B: def __ne__(self, other): return "B.__ne__" assert (A() != B()) is False 来电!=
    3. A.__ne__返回A.__ne__
    4. A.__eq__返回A.__eq__,即True
    5. 这表明在这种情况下,!=方法返回not True方法的反转。因此,False方法的行为与文档中的广告相似。

      使用上面给出的正确实现覆盖A.__ne__方法的默认实现会产生相同的结果。

      A.__eq__实施

      让我们看看当使用__ne__实现覆盖A.__ne__方法的默认实现并且not self == other方法返回A.__ne__时会发生什么:

      not self == other
      1. A.__eq__来电NotImplemented
      2. class A: def __ne__(self, other): return not self == other class B: def __ne__(self, other): return "B.__ne__" assert (A() != B()) is True 来电!=
      3. A.__ne__来电A.__ne__
      4. ==返回==
      5. A.__eq__来电A.__eq__
      6. NotImplemented返回==
      7. B.__eq__返回B.__eq__,即NotImplemented
      8. ==返回A() is B(),即False
      9. A.__ne__方法的默认实现返回not False,而不是True

        现在让我们看看在使用__ne__实现覆盖"B.__ne__"方法的默认实现时会发生什么,而True方法返回的值与A.__ne__不同:

        not self == other
        1. A.__eq__来电NotImplemented
        2. class A: def __eq__(self, other): return True def __ne__(self, other): return not self == other class B: def __ne__(self, other): return "B.__ne__" assert (A() != B()) is False 来电!=
        3. A.__ne__来电A.__ne__
        4. ==返回==
        5. A.__eq__返回A.__eq__,即True
        6. 在这种情况下,A.__ne__方法的默认实现也返回not True

          由于当False方法返回__ne__时,此实现无法复制False方法的默认实现的行为,因此它不正确。 < / p>

答案 4 :(得分:-1)

如果所有__eq____ne____lt____ge____le____gt__对该课程有意义,那么实现__cmp__。否则,就像你正在做的那样,因为Daniel DiPaolo说道(当我测试它而不是查找它时);)