【问题标题】:__lt__ instead of __cmp____lt__ 而不是 __cmp__
【发布时间】:2010-11-06 20:55:51
【问题描述】:

Python 2.x 有两种重载比较运算符的方法,__cmp__ 或“丰富的比较运算符”,例如 __lt__富比较重载据说是首选,但为什么会这样呢?

丰富的比较运算符实现起来更简单,但您必须使用几乎相同的逻辑来实现其中的几个。但是,如果您可以使用内置的 cmp 和元组排序,那么 __cmp__ 会变得非常简单并完成所有比较:

class A(object):
  def __init__(self, name, age, other):
    self.name = name
    self.age = age
    self.other = other
  def __cmp__(self, other):
    assert isinstance(other, A) # assumption for this example
    return cmp((self.name, self.age, self.other),
               (other.name, other.age, other.other))

这种简单性似乎比重载所有 6(!) 个丰富的比较更能满足我的需求。 (但是,如果您依赖“交换的论点”/反映的行为,您可以将其降低到“仅”4,但在我看来,这会导致并发症的净增加。)

如果我只超载__cmp__,是否需要注意任何不可预见的陷阱?

我理解<<===等运算符可以被重载用于其他目的,并且可以返回任何他们喜欢的对象。我不是在问这种方法的优点,而只是问在使用这些运算符进行比较时的差异,就像它们对数字的意义一样。

更新: 作为 Christopher pointed outcmp 在 3.x 中消失。 是否有任何替代方案可以像上述__cmp__ 那样轻松实现比较?

【问题讨论】:

  • 查看我对你最后一个问题的回答,但实际上有一种设计可以让包括你在内的许多类变得更容易(现在你需要一个 mixin、元类或类装饰器来应用它):如果存在 key 特殊方法,则它必须返回一个值元组,并且所有比较器 AND hash 都是根据该元组定义的。当我向他解释时,Guido 喜欢我的想法,但后来我忙于其他事情,从来没有时间写 PEP……也许是 3.2;-)。与此同时,我一直在使用我的 mixin!-)

标签: python operator-overloading


【解决方案1】:

是的,实现一切都很容易,例如__lt__ 带有一个 mixin 类(或者一个元类,或者一个类装饰器,如果你喜欢这样的话)。

例如:

class ComparableMixin:
  def __eq__(self, other):
    return not self<other and not other<self
  def __ne__(self, other):
    return self<other or other<self
  def __gt__(self, other):
    return other<self
  def __ge__(self, other):
    return not self<other
  def __le__(self, other):
    return not other<self

现在你的类可以只定义__lt__ 并从 ComparableMixin 乘以继承(在它需要的任何其他基础之后,如果有的话)。类装饰器将非常相似,只是插入类似的函数作为它正在装饰的新类的属性(结果在运行时可能会在微观上更快,在内存方面的成本同样微小)。

当然,如果你的类有一些特别快速的方法来实现(例如)__eq____ne__,它应该直接定义它们,这样就不会使用 mixin 的版本(例如,@987654328 就是这种情况@) -- 事实上__ne__ 很可能被定义为:

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

但在上面的代码中,我想保持只使用&lt; 的令人愉悦的对称性;-)。 至于为什么 __cmp__ 必须离开,既然我们确实__lt__ 和朋友,为什么要保留另一种不同的方式来做同样的事情呢?在每个 Python 运行时(Classic、Jython、IronPython、PyPy ......)中,它都是如此沉重。 肯定不会有错误的代码是不存在的代码——Python的原则是理想情况下应该有一种明显的方式来执行任务(C在ISO 标准的“C 精神”部分,顺便说一句)。

这并不意味着我们会竭尽全力禁止某些事情(例如,mixin 和类装饰器在某些用途上几乎等效),但它绝对确实意味着我们不喜欢在编译器和/或运行时中携带冗余存在的代码,只是为了支持多种等效方法来执行完全相同的任务。

进一步编辑:实际上有一种更好的方法可以为许多类提供比较和散列,包括问题中的方法——__key__ 方法,正如我在对问题的评论中提到的那样。由于我从来没有为它编写 PEP,如果你喜欢它,你现在必须使用 Mixin (&c) 来实现它:

class KeyedMixin:
  def __lt__(self, other):
    return self.__key__() < other.__key__()
  # and so on for other comparators, as above, plus:
  def __hash__(self):
    return hash(self.__key__())

将一个实例与其他实例的比较归结为将每个实例的一个元组与几个字段进行比较是一种非常常见的情况——然后,散列应该在完全相同的基础上实现。直接需要的__key__特殊方法地址。

【讨论】:

  • 抱歉耽搁了@R。佩特,我决定,因为无论如何我都必须进行编辑,所以我应该提供最彻底的答案,而不是仓促行事(我刚刚再次编辑以提出我从未得到过的旧 key 想法到 PEPping,以及如何使用 mixin 实现它)。
  • 我真的很喜欢那个key的想法,打算用它看看感觉如何。 (虽然命名为 cmp_key 或 _cmp_key 而不是保留名称。)
  • TypeError: Cannot create a consistent method resolution order (MRO) for bases object, ComparableMixin 当我在 Python 3 中尝试此操作时。请参阅 gist.github.com/2696496 的完整代码
  • 在 Python 2.7+/3.2+ 中,您可以使用 functools.total_ordering 而不是构建自己的 ComparableMixim。正如jmagnusson's answer 中所建议的那样
  • 在 Python 3 中使用&lt; 来实现__eq__ 是一个非常糟糕的主意,因为TypeError: unorderable types
【解决方案2】:

为了简化这种情况,Python 2.7+/3.2+ 中有一个类装饰器functools.total_ordering,可用于实现 Alex 的建议。文档中的示例:

@total_ordering
class Student:
    def __eq__(self, other):
        return ((self.lastname.lower(), self.firstname.lower()) ==
                (other.lastname.lower(), other.firstname.lower()))
    def __lt__(self, other):
        return ((self.lastname.lower(), self.firstname.lower()) <
                (other.lastname.lower(), other.firstname.lower()))

【讨论】:

  • total_ordering 没有实现 __ne__,所以要小心!
  • @Flimm,不是,而是__ne__。但那是因为__ne__ 具有委托给__eq__ 的默认实现。所以这里没什么可注意的。
  • 必须定义至少一个排序操作: = .... 如果 ! a
【解决方案3】:

这被PEP 207 - Rich Comparisons覆盖

另外,__cmp__ 在 python 3.0 中消失了。 (请注意,http://docs.python.org/3.0/reference/datamodel.html 上不存在,但http://docs.python.org/2.7/reference/datamodel.html 上存在)

【讨论】:

  • PEP 只关心为什么需要丰富的比较,就像 NumPy 用户希望 A
  • 我没有意识到它肯定会消失,这让我很难过。 (但感谢您指出这一点。)
  • PEP 还讨论了“为什么”它们是首选。从本质上讲,它归结为效率: 1. 无需实现对您的对象没有意义的操作(如无序集合。) 2. 一些集合在某些类型的比较上具有非常有效的操作。如果您定义它们,丰富的比较可以让解释器利用它。
  • Re 1,如果它们没有意义,则不要实现 cmp。 Re 2,拥有这两个选项可以让您根据需要进行优化,同时仍然可以快速进行原型设计和测试。没有人告诉我为什么它被删除。 (对我来说,基本上它归结为开发人员的效率。)丰富的比较是否有可能在 cmp 回退到位的情况下效率较低?这对我来说没有意义。
  • @R. Pate,正如我试图在我的回答中解释的那样,一般性并没有真正的损失(因为 mixin、装饰器或元类可以让您轻松地根据 cmp 的冗余代码——只是为了让 Python 用户以两种等效的方式表达事物——将 100% 违背 Python 的本质。
【解决方案4】:

(已于 2017 年 6 月 17 日编辑,将 cmets 考虑在内。)

我在上面尝试了类似的 mixin 答案。我遇到了“无”的麻烦。这是一个修改后的版本,它使用“None”处理相等比较。 (我认为没有理由为与 None 的不等式比较而烦恼,因为它缺乏语义):


class ComparableMixin(object):

    def __eq__(self, other):
        if not isinstance(other, type(self)): 
            return NotImplemented
        else:
            return not self<other and not other<self

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

    def __gt__(self, other):
        if not isinstance(other, type(self)): 
            return NotImplemented
        else:
            return other<self

    def __ge__(self, other):
        if not isinstance(other, type(self)): 
            return NotImplemented
        else:
            return not self<other

    def __le__(self, other):
        if not isinstance(other, type(self)): 
            return NotImplemented
        else:
            return not other<self    

【讨论】:

  • 您如何看待self 可能是NoneType 的单例None 并同时实现您的ComparableMixin?确实,这个秘诀对 Python 3 很不利。
  • self永远成为None,因此该分支可以完全消失。不要使用type(other) == type(None);只需使用other is None。测试其他类型是否为self 类型的实例,而不是特殊情况None,如果不是,则返回NotImplemented 单例:if not isinstance(other, type(self)): return NotImplemented。对所有方法执行此操作。然后,Python 可以给另一个操作数提供答案的机会。
【解决方案5】:

受 Alex Martelli 的 ComparableMixinKeyedMixin 答案的启发,我想出了以下 mixin。 它允许您实现单个 _compare_to() 方法,该方法使用基于键的比较 类似于KeyedMixin,但允许您的班级根据other 的类型选择最有效的比较键。 (请注意,这个 mixin 对于可以测试是否相等但不能测试顺序的对象没有多大帮助)。

class ComparableMixin(object):
    """mixin which implements rich comparison operators in terms of a single _compare_to() helper"""

    def _compare_to(self, other):
        """return keys to compare self to other.

        if self and other are comparable, this function 
        should return ``(self key, other key)``.
        if they aren't, it should return ``None`` instead.
        """
        raise NotImplementedError("_compare_to() must be implemented by subclass")

    def __eq__(self, other):
        keys = self._compare_to(other)
        return keys[0] == keys[1] if keys else NotImplemented

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

    def __lt__(self, other):
        keys = self._compare_to(other)
        return keys[0] < keys[1] if keys else NotImplemented

    def __le__(self, other):
        keys = self._compare_to(other)
        return keys[0] <= keys[1] if keys else NotImplemented

    def __gt__(self, other):
        keys = self._compare_to(other)
        return keys[0] > keys[1] if keys else NotImplemented

    def __ge__(self, other):
        keys = self._compare_to(other)
        return keys[0] >= keys[1] if keys else NotImplemented

【讨论】:

    猜你喜欢
    • 2013-04-20
    • 1970-01-01
    • 2012-10-06
    • 1970-01-01
    • 2020-06-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多