2017-06-13 23 views
6

Như đã đề cập here,Khi nào __eq__ được gọi là sử dụng hàm băm()?

Dưới mã,

class Person(object): 
    def __init__(self, name, ssn, address): 
     self.name = name 
     self.ssn = ssn 
     self.address = address 
    def __hash__(self): 
     print('in hash') 
     return hash(self.ssn) 
    def __eq__(self, other): 
     print('in eq') 
     return self.ssn == other.ssn 

bob = Person('bob', '1111-222-333', None) 

jim = Person('jim bo', '1111-222-333', 'sf bay area') 


dmv_appointments = {} 
print('calling hash') 
dmv_appointments[bob] = 'tomorrow' 
print('calling hash') 
print(dmv_appointments[jim]) 
print('calling hash again') 
print(dmv_appointments[bob]) 

Output:

calling hash 
in hash 
calling hash 
in hash 
in eq 
tomorrow 
calling hash again 
in hash 
tomorrow 

Câu hỏi:

Tại sao __eq__ được gọi khi truy cập jim nhưng không được gọi là bob?

+4

Đoán của tôi là lần đầu tiên kiểm tra hàm băm, sau đó _identity_ (nhanh hơn) và chỉ _then_ bình đẳng. Vì vậy, khi bạn tra cứu 'bob', nó không phải kiểm tra' __eq__' để xem nó có phải là mục nhập đúng hay không, nhưng đối với 'j'' thì nó sẽ làm. –

+0

Câu hỏi liên quan: https: // stackoverflow.com/q/40917986/1639625 Một bình luận nói điều gì đó về cách nó được thực hiện trong việc thực hiện C. –

+0

@tobias_k: Vâng, đó là một chi tiết thực hiện CPython (thông dịch tham khảo Python) do [sử dụng 'PyObject_RichCompareBool' làm phương thức chuẩn để so sánh các đối tượng ở lớp C] (https://docs.python.org/3/c -api/object.html # c.PyObject_RichCompareBool). Sử dụng hàm đó để kiểm tra bình đẳng có hiệu quả tương đương với 'return x là y hoặc x == y' ở mức Python. – ShadowRanger

Trả lời

8

ngắn câu trả lời: một tra cứu từ điển đầu tiên thực hiện một (rẻ) tham khảo kiểm tra bình đẳng (x is y) khi tìm kiếm một cái xô, và chỉ khi thất bại, một tấm séc (đắt hơn) bình đẳng (x == y) được thực hiện.

Kịch bản

Chức năng __hash__ không không gọi__eq__ nội bộ. Do bạn xây dựng bobjim, không có phương pháp nào được gọi.

Tiếp theo, bạn liên kết bob với 'tomorrow'. Để biết trong đó có từ điển của từ điển, bạn phải lưu trữ bob, bạn tính giá trị băm. Bây giờ, khi bạn đã thực hiện xong, chúng tôi lưu trữ bob (và giá trị trong nhóm chính xác).

Tiếp theo, chúng tôi muốn lấy jim. Để biết được số thùng nào jim, chúng tôi tính giá trị băm. Tiếp theo, chúng tôi bắt đầu tìm kiếm trong nhóm. Thùng chứa sẽ chứa bob. Trước tiên, chúng tôi thực hiện kiểm tra tham chiếu (jim is bob) nhưng không thành công, vì vậy, chúng tôi dự phòng số séc bình đẳng. Việc kiểm tra đó thành công, vì vậy chúng tôi trả lại giá trị tương ứng với bob: 'tomorrow'.

Kịch bản tương tự cũng xảy ra khi chúng tôi muốn tìm kiếm bob: chúng tôi tính giá trị băm, tìm nạp nhóm. Thực hiện kiểm tra tham chiếu trên bob is bob và điều đó thành công. Vì vậy, chúng tôi không cần một (có thể kiểm tra bình đẳng đắt tiền hơn). Chúng tôi chỉ trả lại giá trị 'tomorrow'.

kiểm tra tham khảo

Thực tế là một tấm séc tài liệu tham khảo được thực hiện đầu tiên có thể được chứng minh như sau (không lành mạnh) mã:

class Person(object): 
    def __init__(self, name, ssn, address): 
     self.name = name 
     self.ssn = ssn 
     self.address = address 
    def __hash__(self): 
     print('in hash') 
     return hash(self.ssn) 
    def __eq__(self, other): print('in eq') return False

Ở đây chúng ta quay trở lại luôn False cho sự bình đẳng.Vì vậy, mặc:

>>> bob == bob 
in eq 
False 
>>> bob is bob 
True 

bob không bằng bản thân (điều này thực sự là thiết kế không tốt, vì đối với một cuốn từ điển, nó là một hợp đồng mà một đối tượng là tương đương với bản thân: a tốt bình đẳng liên quanphản, đối xứngtransitive). Tuy nhiên, nếu chúng ta kết hợp với bob'tomorrow', chúng ta vẫn có thể lấy giá trị gắn liền với bob:

>>> dmv_appointments = {} 
>>> dmv_appointments[bob] = 'tomorrow' 
in hash 
>>> dmv_appointments[bob] 
in hash 
'tomorrow' 
+0

Nó thường không yêu cầu tất cả các đối tượng bằng nhau. 'float ('nan')' và 'decimal.Decimal ('nan')' không, ví dụ, và các kiểu đó đi kèm với Python. Nó * là * yêu cầu rằng '==' là một mối quan hệ tương đương cho các khóa dict, do đó, sử dụng một trong những đối tượng 'Person' như một khóa dict phá vỡ hợp đồng cho các khóa dict. – user2357112

+0

@ user2357112: vâng có một số ngoại lệ, vì thường thấy 'NaN' là kết quả" bị hỏng ". Hai 'nan' có nguồn gốc từ các tính toán khác nhau thường không bằng nhau. Nhưng nói chung phản xạ, đối xứng và transitivity là tính chất của một mối quan hệ bình đẳng âm thanh. –

2

Để trả lời câu tiêu đề:

Khi nào __eq__ được gọi sử dụng hash()?

Không bao giờ.


Một câu hỏi khác:

Tại sao __eq__ được kêu gọi truy cập jim nhưng không phải trên bob?

Điều đó phức tạp hơn. Để hiểu rằng bạn cần phải biết làm thế nào một từ điển được thực hiện. Giả sử CPython nó sẽ là một bảng chứa một cột hash, một cột keyvalue cột:

hash  | key  | value 
----------------------------------------- 
    - |  - |  - 
----------------------------------------- 
    - |  - |  - 

Nó sẽ có một kích thước nhất định, nhưng nó sẽ không đủ lớn để chứa tất cả các giá trị có thể hash, vì vậy nó sẽ tính toán vị trí dựa trên hash. Ví dụ: nếu bạn thêm bob nó có thể có (chuỗi băm ngẫu nhiên trong một số phiên bản CPython nhất định để kết quả thực tế sẽ khác nhau) một hash của 7475314405837642385. Giả sử các từ điển có kích thước thực tế của 2 (trong thực tế nó sẽ lớn hơn, nhưng điều đó không cần thiết sẽ lãng phí không gian trong câu trả lời) nó chỉ mất modulo, vì vậy nó sẽ đặt nó vào 7475314405837642385 % 2 == 1:

hash  | key  | value 
----------------------------------------- 
    - |  - |  - 
----------------------------------------- 
747...385| bob | 'tomorrow' 

Khi bạn muốn tìm kiếm một phím nó

  • luôn tính toán hash của tra cứu
  • sau đó nó sẽ tính toán vị trí này.
  • Sau đó, nó so sánh hash của lookup đến hash lưu ở vị trí đó
  • nếu hash es đều bình đẳng thì nó sẽ so sánh các tra cứu và lưu key với PyObject_RichCompareBool.Đó là ý chí:
    • kiểm tra đầu tiên cho bản sắc: lookup is key
    • nếu đó là False nó sẽ kiểm tra lookup == key

Vì vậy, trong trường hợp bạn tra cứu bob:

  • băm: 7475314405837642385
  • po hãng cạnh tranh: 7475314405837642385 % 2 ->1
  • tìm thấy một mục, vì vậy so sánh hash es: 7475314405837642385 == 7475314405837642385
  • đó là bình đẳng để kiểm tra xem có bản sắc: bob is bob ->True

Vì vậy, nó sẽ trả về 'tomorrow' mà không có một kiểm tra bình đẳng. Trong trường hợp thứ hai nó sẽ kiểm tra cho jim:

  • băm: 7475314405837642385
  • số các mục: 7475314405837642385 % 2 ->1
  • tìm thấy một mục, vì vậy so sánh hash es: 7475314405837642385 == 7475314405837642385
  • đó là bình đẳng để kiểm tra danh tính: jim is bob ->False
  • kiểm tra tính bình đẳng jim == bob ->True

Vì vậy, nó trả về 'tomorrow'.


Đây chỉ là xấp xỉ thực hiện thực tế (thiếu một số chi tiết). Nó trở nên phức tạp hơn nếu các es hash không bằng nhau hoặc lookup is not key and lookup != key nhưng điều này không thực sự quan trọng để hiểu hành vi được quan sát mà bạn đã đặt câu hỏi.

Tuy nhiên, tôi thực sự cần phải nói điều này: Những gì bạn đang làm thực sự nguy hiểm vì lớp học của bạn không phải là bất biến. Bạn có thể vô tình làm cho sự xâm nhập từ điển lưu không có sẵn cho bạn:

dmv_appointments = {bob: 1} 
bob.ssn = '1'  # changing ssn changes the hash! 
dmv_appointments[bob] 
--------------------------------------------------------------------------- 
KeyError         Traceback (most recent call last) 
<ipython-input-35-3920ada7bab1> in <module>() 
    15 dmv_appointments = {bob: 1} 
    16 bob.ssn = '1' 
---> 17 dmv_appointments[bob] 

KeyError: <__main__.Person object at 0x000001BD5DDCC470> 

(Nó vẫn có thể làm việc trong trường hợp mới hash bằng với băm "cũ", nhưng điều đó sẽ thay ngẫu nhiên).

Đó là bởi vì khi bạn thay đổi hash trong trường hợp của bạn - từ điển sẽ không cập nhật số đã lưu hash vì nó giả định tất cả các phím là không thay đổi! Vì vậy, từ điển hoặc là giả định nó sẽ được lưu ở vị trí khác hoặc nếu vị trí sẽ (một cách kỳ diệu) giống nhau thì nó sẽ thất bại trong bước mà nó chứa các hash thực sự.

Các vấn đề liên quan