2011-10-04 31 views
22

Theo sau từ this câu hỏi, tôi quan tâm để biết khi nào là băm của đối tượng python được tính?Khi nào hàm băm của đối tượng python được tính và tại sao hàm băm -1 khác nhau?

  1. Lúc __init__ của một ví dụ,
  2. Lần đầu tiên __hash__() được gọi,
  3. Mỗi lần __hash__() được gọi, hoặc
  4. Bất kỳ cơ hội khác mà tôi có thể thiếu?

Có thể điều này thay đổi tùy thuộc vào loại đối tượng không?

Tại sao hash(-1) == -2 trong khi các số nguyên khác bằng giá trị băm của chúng?

+0

3 là ra như tôi đã [đọc] (http://www.laurentluce.com/posts/ python-dictionary-implementation /) nó được lưu trữ trong lần đầu tiên nó được gọi.Tôi sẽ giả sử tùy chọn thứ 2 là đúng nhưng tôi không chắc chắn tôi sẽ không đăng nó như là một câu trả lời :) – rplnt

+0

@ rplnt: sai; đó chỉ đơn thuần là khi nói về một cuốn từ điển. Băm của nó sẽ được lưu trữ trong từ điển, nhưng điều đó không đúng với băm chung. –

+0

@ChrisMorgan Thực ra tôi không nghĩ rằng giá trị băm python 'dict' lưu trữ các giá trị băm của nó. Tất nhiên, các lớp riêng lẻ có thể làm bất cứ điều gì họ thích trong hàm '__hash__' của họ, vì vậy bài báo được đề cập ở trên nói rằng' str' lưu trữ giá trị băm của nó. – max

Trả lời

19

Hàm băm thường được tính mỗi khi được sử dụng, vì bạn có thể dễ dàng kiểm tra chính mình (xem bên dưới). Tất nhiên, bất kỳ đối tượng cụ thể nào đều được tự do lưu vào bộ nhớ cache của nó. Ví dụ: chuỗi CPython thực hiện việc này, nhưng các bộ dữ liệu không (xem ví dụ: this rejected bug report vì lý do).

Giá trị băm -1signalizes an error cho CPython. Điều này là do C không có ngoại lệ, vì vậy nó cần phải sử dụng giá trị trả về. Khi đối tượng của Python là __hash__ trả về -1, CPython sẽ thực sự âm thầm thay đổi thành -2.

Xem cho chính mình:

class HashTest(object): 
    def __hash__(self): 
     print('Yes! __hash__ was called!') 
     return -1 

hash_test = HashTest() 

# All of these will print out 'Yes! __hash__ was called!': 

print('__hash__ call #1') 
hash_test.__hash__() 

print('__hash__ call #2') 
hash_test.__hash__() 

print('hash call #1') 
hash(hash_test) 

print('hash call #2') 
hash(hash_test) 

print('Dict creation') 
dct = {hash_test: 0} 

print('Dict get') 
dct[hash_test] 

print('Dict set') 
dct[hash_test] = 0 

print('__hash__ return value:') 
print(hash_test.__hash__()) # prints -1 
print('Actual hash value:') 
print(hash(hash_test)) # prints -2 
5

Từ here:

Giá trị băm -1 được dành riêng (nó được sử dụng để lỗi cờ trong việc thực hiện C). Nếu thuật toán băm tạo ra giá trị này, chúng tôi chỉ cần sử dụng -2 để thay thế.

Khi băm của số nguyên là chính số nguyên, nó chỉ thay đổi ngay lập tức.

1

Nó rất dễ dàng để thấy rằng tùy chọn # 3 giữ cho người dùng định nghĩa các đối tượng. Điều này cho phép băm thay đổi nếu bạn thay đổi đối tượng, nhưng nếu bạn sử dụng đối tượng làm khóa từ điển, bạn phải chắc chắn ngăn chặn băm thay đổi.

>>> class C: 
    def __hash__(self): 
     print("__hash__ called") 
     return id(self) 


>>> inst = C() 
>>> hash(inst) 
__hash__ called 
43795408 
>>> hash(inst) 
__hash__ called 
43795408 
>>> d = { inst: 42 } 
__hash__ called 
>>> d[inst] 
__hash__ called 

Tùy chọn sử dụng chuỗi # 2: họ tính giá trị băm một lần và lưu vào bộ nhớ cache kết quả. Điều này là an toàn vì các chuỗi không thay đổi được nên băm không bao giờ có thể thay đổi, nhưng nếu bạn phân lớp str kết quả có thể không thay đổi, do đó phương pháp __hash__ sẽ được gọi lại mỗi lần nữa. Tuple thường được coi là không thay đổi, do đó bạn có thể nghĩ rằng băm có thể được lưu trữ, nhưng trên thực tế, băm của tuple phụ thuộc vào hàm băm của nội dung và có thể bao gồm các giá trị có thể thay đổi.

Đối @max người không tin rằng lớp con của str có thể sửa đổi các hash:

>>> class C(str): 
    def __init__(self, s): 
     self._n = 1 
    def __hash__(self): 
     return str.__hash__(self) + self._n 


>>> x = C('hello') 
>>> hash(x) 
-717693723 
>>> x._n = 2 
>>> hash(x) 
-717693722 
+0

Nếu bạn chuyển một bộ chứa các giá trị có thể thay đổi làm đối số cho trong hàm băm, ngoại lệ TypeError sẽ được nâng lên. Vì vậy, đây không phải là lý do mà các bộ nhớ không lưu trữ giá trị băm của chúng. Liên kết ở đầu câu trả lời [@ PetrViktorin ở trên] (http://stackoverflow.com/a/7648538/336527) cung cấp giải thích. Xem thêm [Nhận xét của Guido] (http://mail.python.org/pipermail/python-dev/2003-August/037424.html). Ngoài ra, bạn có chắc chắn băm không được lưu trữ cho các lớp con str? Có vẻ như nó trả về cùng giá trị như str.hash, được lưu trữ tự động. – max

+0

@max, tôi đã thêm một ví dụ cho bạn thấy rằng băm của một lớp con 'str' không được lưu trữ. – Duncan

+0

ah có, đúng .. Tôi đoán tôi đã suy nghĩ nếu bạn không xác định '__hash__', nó được lưu trữ nhưng sau đó nó hiển nhiên vì nó chỉ sử dụng' str .__ hash__' trong trường hợp đó. – max

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