__lt__ thay vì __cmp__


100

Python 2.x có hai cách để nạp chồng toán tử so sánh, __cmp__hoặc "toán tử so sánh phong phú" chẳng hạn như __lt__. Quá tải so sánh phong phú được cho là được ưu tiên hơn, nhưng tại sao điều này lại như vậy?

Các toán tử so sánh phong phú đơn giản hơn để triển khai mỗi toán tử, nhưng bạn phải triển khai một số trong số chúng với logic gần giống nhau. Tuy nhiên, nếu bạn có thể sử dụng thứ tự nội trang cmpvà tuple, thì việc này __cmp__khá đơn giản và đáp ứng tất cả các so sánh:

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))

Sự đơn giản này dường như đáp ứng nhu cầu của tôi tốt hơn nhiều so với việc quá tải tất cả 6 (!) Của các so sánh phong phú. (Tuy nhiên, bạn có thể hạ nó xuống "chỉ" 4 nếu bạn dựa vào "đối số được hoán đổi" / hành vi được phản ánh, nhưng điều đó dẫn đến sự gia tăng phức tạp, theo ý kiến ​​khiêm tốn của tôi.)

Có bất kỳ cạm bẫy nào không lường trước được mà tôi cần lưu ý nếu tôi chỉ quá tải __cmp__không?

Tôi hiểu <, <=, ==vv khai thác có thể bị quá tải cho các mục đích khác, và có thể trả lại bất kỳ đối tượng họ như thế nào. Tôi không hỏi về giá trị của cách tiếp cận đó, mà chỉ hỏi về sự khác biệt khi sử dụng các toán tử này để so sánh theo cùng nghĩa mà chúng có nghĩa đối với các số.

Cập nhật: Như Christopher đã chỉ ra , cmpsẽ biến mất trong 3.x. Có bất kỳ lựa chọn thay thế nào giúp việc thực hiện so sánh dễ dàng như ở trên __cmp__không?


5
Hãy xem câu trả lời của tôi là câu hỏi cuối cùng của bạn, nhưng thực sự có một thiết kế sẽ giúp mọi thứ trở nên dễ dàng hơn đối với nhiều lớp bao gồm cả lớp của bạn (ngay bây giờ bạn cần một mixin, metaclass hoặc trình trang trí lớp để áp dụng nó): nếu có một phương pháp đặc biệt chính , nó phải trả về một bộ giá trị và tất cả bộ so sánh AND băm được xác định theo bộ giá trị đó. Guido thích ý tưởng của tôi khi tôi giải thích nó với anh ấy, nhưng sau đó tôi bận bịu với những việc khác và không bao giờ lo viết PEP ... có thể là 3.2 ;-). Trong khi đó tôi tiếp tục sử dụng mixin tôi cho rằng -!)
Alex Martelli

Câu trả lời:


90

Đúng vậy, thật dễ dàng để triển khai mọi thứ, chẳng hạn như __lt__với lớp mixin (hoặc metaclass, hoặc lớp trang trí nếu sở thích của bạn theo cách đó).

Ví dụ:

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

Bây giờ lớp của bạn có thể xác định chỉ __lt__và nhân thừa kế từ Comp CompareMixin (sau bất kỳ cơ sở nào khác mà nó cần, nếu có). Một trình trang trí lớp sẽ khá giống nhau, chỉ cần chèn các chức năng tương tự làm thuộc tính của lớp mới mà nó đang trang trí (kết quả có thể nhanh hơn kính hiển vi trong thời gian chạy, với chi phí tương đương về bộ nhớ).

Tất nhiên, nếu lớp của bạn có một số cách thực hiện đặc biệt nhanh (ví dụ) __eq____ne__, nó phải xác định chúng trực tiếp để các phiên bản của mixin không được sử dụng (ví dụ: đó là trường hợp cho dict) - trên thực tế, __ne__cũng có thể được xác định để tạo điều kiện đó là:

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

nhưng trong đoạn mã trên, tôi muốn giữ sự đối xứng dễ chịu của việc chỉ sử dụng <;-). Tại sao __cmp__phải đi, vì chúng ta đã__lt__và bạn bè, tại sao lại giữ một cách khác, khác để làm chính xác những điều xung quanh? Nó chỉ là quá nhiều trọng lượng trong mỗi thời gian chạy Python (Classic, Jython, IronPython, PyPy, ...). Mã chắc chắn sẽ không có lỗi là mã không có ở đó - vì nguyên tắc của Python rằng lý tưởng là phải có một cách rõ ràng để thực hiện một nhiệm vụ (C có cùng nguyên tắc trong phần "Spirit of C" của tiêu chuẩn ISO, btw).

Điều này không có nghĩa là chúng tôi cố gắng cấm mọi thứ (ví dụ: sự gần tương đương giữa các mixin và trình trang trí lớp cho một số mục đích sử dụng), nhưng điều đó chắc chắn nghĩa là chúng tôi không muốn mang theo mã trong trình biên dịch và / hoặc thời gian chạy dư thừa chỉ để hỗ trợ nhiều phương pháp tương đương để thực hiện chính xác cùng một tác vụ.

Chỉnh sửa thêm: thực sự có một cách tốt hơn nữa để cung cấp phép so sánh VÀ băm cho nhiều lớp, bao gồm cả lớp trong câu hỏi - một __key__phương pháp, như tôi đã đề cập trong nhận xét của mình cho câu hỏi. Vì tôi chưa bao giờ viết PEP cho nó, nên bạn hiện phải triển khai nó bằng Mixin (& c) nếu bạn thích nó:

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__())

Đây là một trường hợp rất phổ biến đối với việc so sánh của một cá thể với các trường hợp khác là so sánh một bộ tuple cho mỗi trường hợp với một vài trường - và sau đó, việc băm phải được thực hiện trên cơ sở chính xác. Các __key__địa chỉ phương pháp đặc biệt mà cần trực tiếp.


Xin lỗi vì sự chậm trễ @R. Pate, tôi quyết định rằng vì dù sao tôi cũng phải chỉnh sửa, tôi nên cung cấp câu trả lời kỹ lưỡng nhất có thể thay vì vội vàng (và tôi vừa chỉnh sửa lại để đề xuất ý tưởng quan trọng cũ mà tôi chưa bao giờ tìm hiểu về PEPping, cũng như cách để thực hiện nó với một mixin).
Alex Martelli

Tôi thực sự thích ý tưởng chủ đạo đó, sẽ sử dụng nó và xem nó cảm thấy thế nào. (Mặc dù tên cmp_key hoặc _cmp_key thay vì một tên dành riêng.)

TypeError: Cannot create a consistent method resolution order (MRO) for bases object, ComparableMixinkhi tôi thử điều này bằng Python 3. Xem mã đầy đủ tại gist.github.com/2696496
Adam Parkin

2
Trong Python 2.7 + / 3.2 +, bạn có thể sử dụng functools.total_orderingthay vì xây dựng của riêng bạn ComparableMixim. Như đã đề xuất trong câu trả lời của jmagnusson
Ngày

4
Sử dụng <để triển khai __eq__trong Python 3 là một ý tưởng khá tồi, bởi vì TypeError: unorderable types.
Antti Haapala

49

Để đơn giản hóa trường hợp này, có một trình trang trí lớp trong Python 2.7 + / 3.2 +, functools.total_ordering , có thể được sử dụng để triển khai những gì Alex gợi ý. Ví dụ từ tài liệu:

@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()))

9
total_orderingkhông thực hiện __ne__mặc dù, vì vậy hãy coi chừng!
Flimm

3
@Flimm, nó không, nhưng __ne__. nhưng đó là bởi vì __ne__có triển khai mặc định ủy quyền __eq__. Vì vậy, không có gì để xem ở đây.
Jan Hudec

phải xác định ít nhất một thao tác đặt hàng: <> <=> = .... eq là không cần thiết vì tổng đơn hàng nếu! a <b và b <a thì a = b
Xanlantos

9

Điều này được đề cập trong PEP 207 - So sánh phong phú

Ngoài ra, __cmp__biến mất trong python 3.0. (Lưu ý rằng nó không có trên http://docs.python.org/3.0/reference/datamodel.html nhưng nó có trên http://docs.python.org/2.7/reference/datamodel.html )


PEP chỉ quan tâm đến lý do tại sao cần so sánh phong phú, theo cách mà người dùng NumPy muốn A <B trả về một chuỗi.

Tôi đã không nhận ra rằng nó chắc chắn sẽ biến mất, điều này làm tôi buồn. (Nhưng nhờ sự chỉ ra rằng.)

PEP cũng thảo luận về "tại sao" chúng được ưu tiên. Về cơ bản, nó mang lại hiệu quả: 1. Không cần thực hiện các thao tác không có ý nghĩa đối với đối tượng của bạn (như các tập hợp không có thứ tự.) 2. Một số tập hợp có các hoạt động rất hiệu quả đối với một số kiểu so sánh. Các so sánh phong phú cho phép thông dịch viên tận dụng lợi thế đó nếu bạn xác định chúng.
Christopher

1
Re 1, Nếu chúng không có ý nghĩa, thì đừng triển khai cmp . Re 2, có cả hai tùy chọn có thể cho phép bạn tối ưu hóa khi cần thiết, trong khi vẫn nhanh chóng tạo mẫu và thử nghiệm. Không ai cho tôi biết tại sao nó bị xóa. (Về cơ bản, nó phụ thuộc vào hiệu quả của nhà phát triển đối với tôi.) Có thể so sánh phong phú kém hiệu quả hơn với dự phòng cmp tại chỗ? Điều đó không có ý nghĩa đối với tôi.

1
@R. Pate, như tôi cố gắng giải thích trong câu trả lời của mình, không có sự mất mát thực sự nào về tính tổng quát (vì mixin, decorator hoặc metaclass, cho phép bạn dễ dàng xác định mọi thứ chỉ về <nếu bạn muốn) và do đó, tất cả các triển khai Python có thể thực hiện được mã dự phòng rơi trở lại cmp forevermore - chỉ để cho phép người dùng Python diễn đạt mọi thứ theo hai cách tương đương - sẽ chạy 100% theo nguyên tắc của Python.
Alex Martelli

2

(Đã chỉnh sửa 17/6/17 để xem xét các nhận xét.)

Tôi đã thử câu trả lời mixin có thể so sánh ở trên. Tôi gặp rắc rối với "Không". Đây là phiên bản đã sửa đổi để xử lý các so sánh bình đẳng với "Không có". (Tôi thấy không có lý do gì để bận tâm đến việc so sánh bất bình đẳng với Không có vì thiếu ngữ nghĩa):


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    

Làm thế nào để bạn nghĩ rằng đó selfcó thể là singleton Nonecủa NoneTypevà đồng thời thực hiện của bạn ComparableMixin? Và quả thực công thức này có hại cho Python 3.
Antti Haapala

3
selfsẽ không bao giờ được None, vì vậy chi nhánh có thể đi hoàn toàn. Không sử dụng type(other) == type(None); đơn giản là sử dụng other is None. Thay vì chuyên vỏ None, kiểm tra nếu các loại khác là một thể hiện của các loại hình self, và trả lại NotImplementedsingleton nếu không muốn nói: if not isinstance(other, type(self)): return NotImplemented. Làm điều này cho tất cả các phương pháp. Sau đó, Python sẽ có thể cho toán hạng khác cơ hội để cung cấp câu trả lời.
Martijn Pieters

1

Lấy cảm hứng từ câu trả lời ComparableMixin& KeyedMixincâu trả lời của Alex Martelli , tôi đã nghĩ ra bản mixin sau. Nó cho phép bạn triển khai một _compare_to()phương pháp duy nhất , sử dụng các phép so sánh dựa trên khóa tương tự như KeyedMixin, nhưng cho phép lớp của bạn chọn khóa so sánh hiệu quả nhất dựa trên kiểu của other. (Lưu ý rằng mixin này không giúp ích nhiều cho các đối tượng có thể được kiểm tra về sự bình đẳng nhưng không theo thứ tự).

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.