代码之家  ›  专栏  ›  技术社区  ›  Roger Pate

__而不是cmp__

  •  92
  • Roger Pate  · 技术社区  · 17 年前

    __cmp__ 或“丰富的比较运算符”,如 __lt__ . 据说富比较重载是首选,但为什么会这样呢?

    富比较运算符更容易实现,但必须使用几乎相同的逻辑实现其中的几个。但是,如果您可以使用内置的 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,但我的拙见是,这会导致复杂性的净增加。)

    ?

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

    更新: 克里斯托弗 pointed out , 有没有其他方法可以让实现比较像上面那样简单 __化学机械抛光__

    5 回复  |  直到 9 年前
        1
  •  99
  •   Alex Martelli    17 年前

    是的,这是很容易实施的一切方面,例如。 __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
    

    现在,您的类可以定义 __中尉__ 和乘法继承自ComparableMixin(在它需要的任何其他基之后,如果有的话)。类装饰器非常相似,只需插入类似的函数作为它所装饰的新类的属性(结果可能在运行时在微观上更快,在内存方面的开销也同样小)。

    当然,如果您的类有一些特别快速的实现方法(例如) __eq__ 和 __ne__ ,它应该直接定义它们,以便不使用mixin的版本(例如,对于 dict )事实上 __东北__ 很可能被定义为促进这一点:

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

    但是在上面的代码中,我想保持只使用 < ;-). 为什么 __cmp__ 我们必须走了,因为我们 有 __中尉__ 朋友们,为什么要用另一种不同的方式做同样的事情呢?在每一个Python运行时(Classic、Jython、IronPython、PyPy等等),它都是非常沉重的负担。代码 “不会有bug”是不存在的代码——Python的原则是,理想情况下应该有一种明显的方式来执行任务(顺便说一句,C在ISO标准的“C精神”部分中有相同的原则)。

    这并不意味着我们会不遗余力地禁止某些事情(例如,对于某些用途,mixin和类decorator之间几乎是等价的),但这是肯定的 做

    进一步编辑:实际上有一种更好的方法可以为许多类提供比较和散列,包括问题中提到的——a __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__())
    

    一个实例与其他实例的比较通常归结为用几个字段比较每个实例的一个元组——然后,哈希应该在完全相同的基础上实现。这个 特殊方法直接解决了需要的问题。

        2
  •  53
  •   jmagnusson    14 年前

    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()))
    
        4
  •  3
  •   Gabriel Ferrer    9 年前

    我尝试了上面的可比混合答案。我遇到了“没有”的麻烦。这是一个修改过的版本,用于处理与“无”的相等性比较。(我认为没有理由对不平等性进行比较,因为它们都缺乏语义):

    
    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    
    
        5
  •  1
  •   Eli Collins    14 年前

    灵感来源于Alex Martelli的 ComparableMixin KeyedMixin 答案是,我想出了下面的混音。 _compare_to() 方法,该方法使用基于键的比较 ,但允许类根据类型选择最有效的比较键 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