2009-06-30 30 views
84

Python 2.x có hai cách để quá tải 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, nhưng tại sao lại như vậy?__lt__ thay vì __cmp__

Các toán tử so sánh phong phú đơn giản hơn để thực hiện từng thao tác, nhưng bạn phải triển khai một vài trong số chúng với logic gần như giống hệt nhau. Tuy nhiên, nếu bạn có thể sử dụng được xây dựng trong cmp và tuple đặt hàng, sau đó __cmp__ bị 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)) 

đơ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 quá tải tất cả 6 của những người giàu (!) so sánh. (Tuy nhiên, bạn có thể nhận được nó xuống "chỉ" 4 nếu bạn dựa vào "hành vi hoán đổi"/phản ánh hành vi, nhưng điều đó dẫn đến sự gia tăng ròng của biến chứng, theo ý kiến ​​khiêm tốn của tôi.)

Có bất kỳ những cạm bẫy không lường trước được Tôi cần phải được biết nếu tôi chỉ quá tải __cmp__?

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

Cập nhật: Vì Christopher pointed out, cmp sẽ biến mất trong 3.x. Có bất kỳ lựa chọn thay thế nào thực hiện so sánh triển khai dễ dàng như trên __cmp__ không?

+4

Xem câu trả lời của tôi, nhưng thực sự có thiết kế giúp mọi thứ trở nên dễ dàng hơn cho nhiều lớp bao gồm bạn (ngay bây giờ bạn cần mixin, metaclass hoặc trang trí lớp để áp dụng): __key__ phương thức đặc biệt có mặt, nó phải trả về một bộ các giá trị, và tất cả các bộ so sánh và __hash__ được định nghĩa theo các bộ tuple đó. Guido thích ý tưởng của tôi khi tôi giải thích nó cho anh ta, nhưng sau đó tôi đã bận rộn với những thứ khác và không bao giờ có xung quanh để viết một PEP ... có lẽ cho 3,2 ;-). Trong khi đó tôi tiếp tục sử dụng mixin của tôi cho điều đó! -) –

Trả lời

79

Vâng, thật dễ dàng để thực hiện mọi thứ về mặt, ví dụ: __lt__ với một lớp mixin (hoặc một metaclass, hoặc một trang trí lớp nếu hương vị của bạn chạy 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 học của bạn có thể xác định chỉ __lt__ và nhân kế thừa từ ComparableMixin (sau khi bất cứ điều gì các căn cứ khác 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 hàm tương tự như các thuộc tính của lớp mới đó là trang trí (kết quả có thể nhanh hơn về mặt thời gian trong thời gian chạy, với chi phí bằng nhau về mặt bộ nhớ). Tất nhiên, nếu lớp học của bạn có một số cách thực hiện nhanh nhất (ví dụ) __eq____ne__, cần xác định chúng trực tiếp để các phiên bản mixin không sử dụng (ví dụ, trường hợp là dict) - trong thực tế __ne__ cũng có thể được xác định để tạo điều kiện đó như:

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

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

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

Chỉnh sửa thêm: thực tế còn có cách tốt hơn để cung cấp so sánh và băm cho nhiều lớp, bao gồm cả câu hỏi trong câu hỏi - phương pháp __key__, như tôi đã đề cập trong nhận xét của tôi cho câu hỏi. Vì tôi không bao giờ có xung quanh để viết PEP cho nó, bạn đang phải thực hiện nó với một 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__()) 

Đó là một trường hợp rất phổ biến để so sánh của một ví dụ với các trường hợp khác để đun sôi xuống để so sánh một tuple cho mỗi một vài trường - và sau đó, băm nên được triển khai trên cùng một cơ sở. Các địa chỉ phương thức đặc biệt __key__ cần trực tiếp.

+0

Xin lỗi vì sự chậm trễ @R. Pate, tôi quyết định rằng kể từ khi tôi đã phải chỉnh sửa anyway tôi nên cung cấp các câu trả lời toàn diện nhất tôi có thể thay vì vội vã (và tôi đã chỉ cần chỉnh sửa một lần nữa để đề nghị ý tưởng __key__ cũ của tôi mà tôi không bao giờ có xung quanh để PEPping, cũng như làm thế nào để thực hiện nó với một mixin). –

+0

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

+0

'TypeError: Không thể tạo một thứ tự độ phân giải phương thức nhất quán (MRO) cho đối tượng base, ComparableMixin' khi tôi thử điều này trong Python 3. Xem mã đầy đủ tại https: // gist.github.com/2696496 –

8

này được bao phủ bởi PEP 207 - Rich Comparisons

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ó là trên http://docs.python.org/2.7/reference/datamodel.html)

+0

PEP chỉ liên quan đến lý do tại sao các so sánh phong phú là cần thiết, theo cách người dùng NumPy muốn A

+0

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

+0

PEP cũng thảo luận về "tại sao" chúng được ưa thích. Về cơ bản, nó giảm xuống hiệu quả: 1. Không cần phải 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 bộ sưu tập không theo thứ tự.) 2. Một số bộ sưu tập có hoạt động rất hiệu quả trên một số loại so sánh. So sánh phong phú cho phép người phiên dịch tận dụng lợi thế của điều đó nếu bạn định nghĩa chúng. – Christopher

0

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

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


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  
+0

Làm thế nào để bạn nghĩ rằng 'self' có thể là singleton *' None' * của 'NoneType' và đồng thời thực hiện' ComparableMixin' của bạn? Và thực sự công thức này không tốt cho Python 3. –

+0

'self' sẽ ** không bao giờ ** là' None', vì vậy nhánh đó có thể hoàn toàn. Không sử dụng 'type (other) == type (None)'; chỉ cần sử dụng 'other is None'. Thay vì vỏ đặc biệt 'None', hãy kiểm tra xem loại khác có phải là một thể hiện của kiểu' self', và trả về singlet 'NotImplemented' nếu không:' if not isinstance (other, type (self)): return NotImplemented '. Làm điều này cho tất cả các phương pháp. Python sau đó sẽ có thể cung cấp cho toán hạng khác một cơ hội để cung cấp một câu trả lời thay thế. –

36

Để đơn giản hóa trường hợp này có một trang trí lớp bằng Python 2.7 +/3.2 +, functools.total_ordering, có thể được sử dụng để thực hiện những gì Alex gợi ý. Ví dụ từ các 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())) 
+1

+1 để sử dụng pin đi kèm :) – Day

+8

'total_ordering' không thực hiện' __ne__', vì vậy hãy chú ý! – Flimm

+2

@Flimm, nó không, nhưng '__ne__'. nhưng đó là vì '__ne__' có cài đặt mặc định mà đại biểu cho' __eq__'. Vì vậy, không có gì để xem ra ở đây. –

0

Lấy cảm hứng từ ComparableMixin & KeyedMixin câu trả lời Alex Martelli, tôi đã đưa ra mixin sau. Nó cho phép bạn thực hiện một phương thức _compare_to() đơn, sử dụng các 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 loại 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 được đặt hàng).

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