2012-12-10 16 views
6

Có phải tôi hoặc python bị nhầm lẫn với mã sau không? Tôi mong chờ __le__ được gọi bằng a <= ab, không __ge__:lỗi python với __le__, __ge__?

#!/usr/bin/env python2 

class B(object): 
    def __ge__(self, other): 
     print("__ge__ unexpectedly called") 

class A(object): 
    def __le__(self, other): 
     print("__le__ called") 

class AB(A, B): 
    pass 

a = A() 
ab = AB() 

a <= ab # --> __ge__ unexpectedly called 
ab <= a # --> __le__ called 

tôi nhận được hành vi tương tự với python 2.7, 3.2 và 1.9 PyPy.

Tôi có thể làm gì để nhận được __le__ được gọi thay vì __ge__ ??

+2

Tại sao bạn có tất cả những thứ phức tạp với hàm 'make', khi bạn chỉ có thể làm' a = A() 'và' ab = AB() 'và nhận các đối tượng giống nhau? (Tôi có thể thấy lý do tại sao nó có thể hữu ích trong chương trình thực sự của bạn, nhưng nó không ảnh hưởng đến ví dụ này, và nó có lẽ sẽ ném ra một số người muốn điều tra và hy vọng câu trả lời.) – abarnert

+0

Thật vậy, phiên bản đơn giản bạn đề xuất cũng có hành vi tương tự. Bây giờ sửa chữa. – user474491

Trả lời

8

Câu trả lời ngắn gọn là họ muốn cho phép AB ghi đè hành vi từ A. Python không thể gọi AB.__lt__(a, ab), vì a có thể không hợp lệ self cho phương thức AB, do đó, thay vào đó, nó gọi là AB.__gt__(ab, a), hợp lệ.

Câu trả lời dài phức tạp hơn một chút.

Theo các tài liệu cho rich comparison operators:

Không có phiên bản hoán đổi đối số của các phương pháp này (được sử dụng khi lập luận trái không hỗ trợ các hoạt động nhưng lập luận đúng không); thay vào đó, __lt__()__gt__() là sự phản ánh của nhau, __le__()__ge__() là sự phản ánh của nhau và __eq__()__ne__() là sự phản ánh của riêng chúng.

Nói cách khác, x <= y sẽ gọi y.__ge__(x) chính xác các trường hợp tương tự nơi x+y sẽ gọi y.__radd__(x). Để so sánh:

>>> class X(object): 
...  def __add__(self, other): 
...   print('X.add') 
>>> class Y(object): 
...  def __radd__(self, other): 
...   print('Y.radd') 
>>> class XY(X, Y): 
...  pass 
>>> x, xy = X(), XY() 
>>> x + xy 
Y.radd 

Theo các tài liệu cho reflected operators:

Những phương pháp này được gọi là để thực hiện các phép tính số học nhị phân ... với phản ánh (hoán đổi) toán hạng. Các chức năng này chỉ được gọi nếu toán hạng bên trái không hỗ trợ thao tác tương ứng và các toán hạng có các loại khác nhau…

Lưu ý: Nếu kiểu toán hạng phải là phân lớp của kiểu toán hạng trái và phân lớp đó cung cấp phản ánh phương thức cho phép toán, phương thức này sẽ được gọi trước phương thức không được phản xạ của toán hạng trái. Hành vi này cho phép các lớp con ghi đè hoạt động của tổ tiên của họ.

Vì vậy, bởi vì XY là một lớp con của X, XY.__radd__ được ưu tiên hơn X.__add__. Và, tương tự, vì AB là một phân lớp của A, AB.__ge__ được ưu tiên hơn A.__le__.

Điều này có lẽ phải được ghi lại tốt hơn.Để tìm ra, bạn phải bỏ qua parenthetical "được sử dụng khi đối số bên trái không hỗ trợ thao tác nhưng đối số đúng", bạn cần tìm kiếm các toán tử hoán đổi thông thường (không có liên kết hoặc thậm chí là đề cập đến) , ở đây), sau đó bỏ qua từ ngữ có nội dung "Các hàm này chỉ được gọi nếu toán hạng bên trái không hỗ trợ thao tác tương ứng" và xem "Ghi chú", điều này mâu thuẫn với những gì ở trên… Cũng lưu ý rằng các tài liệu nói rõ ràng, " Không có mối quan hệ ngụ ý giữa các toán tử so sánh ", chỉ có một đoạn trước khi mô tả các trường hợp hoán đổi, trong đó ngụ ý chính xác các mối quan hệ như vậy ...

Cuối cùng, trường hợp này có vẻ lạ, vì tự mình ghi đè __ge__, chỉ thừa kế nó từ B, không biết gì về A và không liên quan đến nó. Có lẽ B không có ý định có các lớp con của nó ghi đè hành vi của A. Nhưng nếu B được sử dụng như một bản mix cho các lớp học có kích thước A, có thể là sẽ có ý định ghi đè chính xác như vậy. Và ở mức độ nào đó, quy tắc có lẽ đã đủ phức tạp mà không đi vào nơi mà mỗi phương pháp đến từ MRO. Bất kể lý do gì, nơi mà __ge__ xuất phát từ không liên quan; nếu nó có trên lớp con, nó sẽ được gọi.

Đối với bổ sung cuối cùng, câu hỏi của bạn, "Tôi có thể làm gì để có được __le__ gọi thay vì __ge__ ??" ... tốt, bạn thực sự không thể, bất kỳ hơn bạn có thể nhận X.__add__ gọi thay vì XY.__radd__. Tất nhiên bạn luôn có thể triển khai AB.__ge__ (hoặc XY.__radd__) gọi A.__le__ (hoặc X.__add__), nhưng có lẽ dễ dàng hơn khi chỉ thực hiện AB.__ge__ theo cách như vậy với đối số khác của nó ở vị trí đầu tiên A. Ngoài ra, bạn có thể loại bỏ các thừa kế và tìm một số cách khác để mô hình bất cứ điều gì bạn đã được mô hình theo cách đó. Hoặc bạn có thể gọi một cách rõ ràng a.__le__(ab) thay vì a<=ab. Nhưng nếu không, nếu bạn thiết kế các lớp học theo cách tận dụng "không có mối quan hệ ngụ ý" để làm điều gì đó kỳ lạ, bạn đã bị các tài liệu lừa dối và sẽ phải thiết kế lại chúng bằng cách nào đó.

+0

Tôi biết điều đó, nhưng tại sao nó lại gọi '__ge__' khi' __le__' có sẵn? – user474491

+1

Bởi vì "loại toán hạng bên phải là một phân lớp của kiểu toán hạng bên trái và phân lớp đó cung cấp phương thức phản xạ", cho phép các lớp con ghi đè hoạt động của tổ tiên ". (Nếu bạn hỏi tại sao nó lại quan trọng vì 'AB' thừa hưởng' __ge__' từ 'B' thay vì xác định nó trực tiếp, điều đó không được xem xét bởi luật. Có lẽ vì mọi thứ đã đủ phức tạp mà không cần thêm để trộn.) – abarnert

+2

@abarnert được đề cập trong câu trả lời của mình (+1, BTW) rằng nó mang lại ưu tiên lớp có nguồn gốc cao hơn, để cho phép nó ghi đè hành vi. – jimhark