Hàm băm làm gì trong python?


86

Tôi đã thấy một ví dụ về mã trong đó hashhàm được áp dụng cho một tuple. Kết quả là nó trả về một số nguyên âm. Tôi tự hỏi chức năng này làm gì? Google không giúp. Tôi đã tìm thấy một trang giải thích cách tính hàm băm nhưng nó không giải thích tại sao chúng ta cần hàm này.


8
Bạn có nhìn vào tài liệu ...
TerryA

đi đến liên kết này (tài liệu chính thức). Nó chỉ định mọi thứ. đi đến liên kết !
tailor_raj

2
Tôi thích rằng câu hỏi không phải là lặp lại "nó là gì" mà là "tại sao chúng ta cần nó".
dnozay

liên kết chính thức rất khó hiểu
Rasmi Ranjan Nayak

Câu trả lời:


148

Hàm băm là một số nguyên có kích thước cố định xác định một giá trị cụ thể . Mỗi giá trị cần có hàm băm riêng, vì vậy, đối với cùng một giá trị, bạn sẽ nhận được cùng một hàm băm ngay cả khi nó không cùng một đối tượng.

>>> hash("Look at me!")
4343814758193556824
>>> f = "Look at me!"
>>> hash(f)
4343814758193556824

Giá trị băm cần được tạo theo cách mà các giá trị kết quả được phân phối đồng đều để giảm số lượng xung đột băm mà bạn nhận được. Xung đột băm là khi hai giá trị khác nhau có cùng một băm. Do đó, những thay đổi tương đối nhỏ thường dẫn đến các hàm băm rất khác nhau.

>>> hash("Look at me!!")
6941904779894686356

Những con số này rất hữu ích, vì chúng cho phép tra cứu nhanh các giá trị trong một bộ sưu tập lớn các giá trị. Hai ví dụ về việc sử dụng chúng là Python setdict. Trong câu a list, nếu bạn muốn kiểm tra xem một giá trị có trong danh sách hay không, với if x in values:, Python cần xem qua toàn bộ danh sách và so sánh xvới từng giá trị trong danh sách values. Điều này có thể mất nhiều thời gian trong một thời gian dài list. Nói cách khác set, Python theo dõi từng hàm băm và khi bạn nhập if x in values:, Python sẽ nhận giá trị băm cho x, tìm kiếm giá trị đó trong cấu trúc bên trong và sau đó chỉ so sánh xvới các giá trị có cùng hàm băm x.

Phương pháp luận tương tự cũng được sử dụng để tra từ điển. Điều này làm cho việc tra cứu trong setdictrất nhanh, trong khi tra cứu trong listthì chậm. Điều đó cũng có nghĩa là bạn có thể có các đối tượng không thể băm trong a list, nhưng không phải trong a sethoặc dưới dạng các khóa trong a dict. Ví dụ điển hình về các đối tượng không thể băm là bất kỳ đối tượng nào có thể thay đổi được, nghĩa là bạn có thể thay đổi giá trị của nó. Nếu bạn có một đối tượng có thể thay đổi, nó không nên được băm, vì hàm băm của nó sau đó sẽ thay đổi theo thời gian tồn tại của nó, điều này sẽ gây ra nhiều nhầm lẫn, vì một đối tượng có thể kết thúc bằng giá trị băm sai trong từ điển.

Lưu ý rằng hàm băm của một giá trị chỉ cần giống nhau cho một lần chạy Python. Trên thực tế, trong Python 3.3, chúng sẽ thay đổi cho mỗi lần chạy Python mới:

$ /opt/python33/bin/python3
Python 3.3.2 (default, Jun 17 2013, 17:49:21) 
[GCC 4.6.3] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> hash("foo")
1849024199686380661
>>> 
$ /opt/python33/bin/python3
Python 3.3.2 (default, Jun 17 2013, 17:49:21) 
[GCC 4.6.3] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> hash("foo")
-7416743951976404299

Điều này làm cho việc đoán giá trị băm của một chuỗi nhất định sẽ khó hơn, đây là một tính năng bảo mật quan trọng cho các ứng dụng web, v.v.

Do đó, giá trị băm không nên được lưu trữ vĩnh viễn. Nếu bạn cần sử dụng các giá trị băm một cách lâu dài, bạn có thể xem các loại băm "nghiêm trọng" hơn, các hàm băm mật mã , có thể được sử dụng để tạo tổng kiểm tra có thể xác minh của tệp, v.v.


11
Liên quan đến va chạm băm tiềm năng: hash(-1) == hash(-2)(runnin Python 2.7)
Matthias

2
Tôi đang chạy Python 3.6.1 và có xung đột.
The_Martian

hash(-1) == hash(-2)vẫn tồn tại cho đến ngày nay. May mắn thay, nó không ảnh hưởng xấu đến việc tra cứu từ điển và thiết lập. Tất cả các số nguyên khác tự igiải quyết cho hash(i)ngoại trừ -1.
Chris Conlan

35

TL; DR:

Vui lòng tham khảo bảng chú giải thuật ngữ : hash()được sử dụng như một phím tắt để so sánh các đối tượng, một đối tượng được coi là có thể băm nếu nó có thể được so sánh với các đối tượng khác. đó là lý do tại sao chúng tôi sử dụng hash(). Nó cũng được sử dụng để truy cập dictsetcác phần tử được triển khai dưới dạng bảng băm có thể thay đổi kích thước trong CPython .

Cân nhắc kỹ thuật

  • thường thì việc so sánh các đối tượng (có thể liên quan đến một số cấp đệ quy) là tốn kém.
  • tốt hơn là, hash()hàm là một cấp độ lớn (hoặc một số) ít tốn kém hơn.
  • so sánh hai hàm băm dễ dàng hơn so sánh hai đối tượng, đây là vị trí của phím tắt.

Nếu bạn đọc về cách các từ điển được triển khai , chúng sử dụng bảng băm, có nghĩa là lấy một khóa từ một đối tượng là một tảng đá góc để lấy các đối tượng trong từ điển trong O(1). Tuy nhiên, điều đó rất phụ thuộc vào hàm băm của bạn để chống va chạm . Các trường hợp xấu nhất để có được một mục trong từ điển thực sự O(n).

Lưu ý rằng, các đối tượng có thể thay đổi thường không thể băm được. Thuộc tính có thể băm có nghĩa là bạn có thể sử dụng một đối tượng làm khóa. Nếu giá trị băm được sử dụng làm khóa và nội dung của cùng một đối tượng đó thay đổi, thì hàm băm sẽ trả về giá trị gì? Nó là cùng một khóa hay một khóa khác? Nó phụ thuộc vào cách bạn xác định hàm băm của mình.

Học theo ví dụ:

Hãy tưởng tượng chúng ta có lớp học này:

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

Xin lưu ý: tất cả đều dựa trên giả định rằng SSN không bao giờ thay đổi đối với một cá nhân (thậm chí không biết nơi thực sự xác minh thông tin đó từ nguồn có thẩm quyền).

Và chúng tôi có Bob:

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

Bob đến gặp thẩm phán để đổi tên:

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

Đây là những gì chúng tôi biết:

>>> bob == jim
True

Nhưng đây là hai đối tượng khác nhau được cấp phát bộ nhớ khác nhau, giống như hai bản ghi khác nhau của cùng một người:

>>> bob is jim
False

Bây giờ đến phần mà hash () rất hữu ích:

>>> dmv_appointments = {}
>>> dmv_appointments[bob] = 'tomorrow'

Đoán xem:

>>> dmv_appointments[jim] #?
'tomorrow'

Từ hai bản ghi khác nhau, bạn có thể truy cập cùng một thông tin. Bây giờ hãy thử điều này:

>>> dmv_appointments[hash(jim)]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 9, in __eq__
AttributeError: 'int' object has no attribute 'ssn'
>>> hash(jim) == hash(hash(jim))
True

Chuyện gì vừa xảy ra? Đó là một vụ va chạm. Vì hash(jim) == hash(hash(jim))cả hai đều là số nguyên btw, chúng ta cần so sánh đầu vào của __getitem__với tất cả các mục xung đột. Nội trang intkhông có một ssnthuộc tính để nó di chuyển.

>>> del Person.__eq__
>>> dmv_appointments[bob]
'tomorrow'
>>> dmv_appointments[jim]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: <__main__.Person object at 0x7f611bd37110>

Trong ví dụ cuối cùng này, tôi chỉ ra rằng ngay cả khi có va chạm, so sánh được thực hiện, các đối tượng không còn bằng nhau nữa, có nghĩa là nó đã nâng thành công a KeyError.


Giải thích thực sự tiện dụng. Là một người mới, điều này đã giúp tôi tìm ra cách tạo lớp có thể được đưa vào các tập hợp và sử dụng chúng làm khóa cho từ điển / bảng băm. Ngoài ra, nếu tôi thực hiện bộ sưu tập [hashable_obj] = hashable_obj, tôi có thể nhận được một con trỏ đến phiên bản đó sau này. Nhưng hãy cho tôi biết nếu có cách tốt hơn để theo dõi các bộ sưu tập như vậy.
PaulDong

@dnozay Tuy nhiên, đầu ra của hash()là một số nguyên có kích thước cố định, có thể gây ra xung đột
trao đổi quá

2
Ai đó có thể nói rõ hơn về việc sử dụng __eq__trong ví dụ trên. Nó có được gọi bởi từ điển khi nó đang cố gắng so sánh khóa nó nhận được với tất cả các khóa nó có không? Như vậy bởi delcác __eq__phương pháp trong ví dụ cuối cùng, từ điển không có gì để gọi sử dụng để xác định tương đương của khóa nó đã nhận được với các phím nó có?
Jet Blue

1
@JetBlue Phần giải thích "collosion" không đầy đủ trong ví dụ với key hash(jim). Person.__eq__được gọi là vì khóa hiện tại có cùng hàm băm hash(jim)để đảm bảo rằng đây là khóa phù hợp Person.__eq__được sử dụng. Nó errs vì nó giả định rằng other, đó là int, có một ssnthuộc tính. Nếu hash(jim)khóa không tồn tại trong từ điển __eq__sẽ không được gọi. Điều này giải thích khi tra cứu khóa có thể là O (n): khi tất cả các mục có cùng một hàm băm __eq__phải được sử dụng cho tất cả chúng, ví dụ trong trường hợp khóa không tồn tại.
WloHu

1
Mặc dù tôi hiểu sự quan tâm sư phạm của ví dụ của bạn, sẽ không đơn giản hơn nếu chỉ viết dmv_appointments[bob.ssn] = 'tomorrow', loại bỏ sự cần thiết phải xác định một __hash__phương pháp? Tôi hiểu rằng thêm 4 ký tự cho mỗi cuộc hẹn bạn viết và đọc, nhưng nó có vẻ rõ ràng hơn đối với tôi.
Alexis

3

Tài liệuhash() Python cho trạng thái:

Giá trị băm là số nguyên. Chúng được sử dụng để so sánh nhanh các khóa từ điển trong quá trình tra cứu từ điển.

Từ điển Python được triển khai dưới dạng bảng băm. Vì vậy, bất kỳ lúc nào bạn sử dụng từ điển, hash()sẽ được gọi trên các khóa mà bạn chuyển vào để gán hoặc tra cứu.

Ngoài ra, các tài liệu chodict trạng thái loại :

Các giá trị không thể băm , nghĩa là, các giá trị chứa danh sách, từ điển hoặc các loại có thể thay đổi khác (được so sánh theo giá trị chứ không phải theo nhận dạng đối tượng) không được sử dụng làm khóa.


1

Hàm băm được sử dụng bởi từ điển và bộ để tra cứu nhanh đối tượng. Một điểm khởi đầu tốt là bài viết trên Wikipedia về bảng băm .


-2

Bạn có thể sử dụng Dictionarykiểu dữ liệu trong python. Nó rất giống với hash — và nó cũng hỗ trợ lồng vào nhau, tương tự như với hash lồng nhau.

Thí dụ:

dict = {'Name': 'Zara', 'Age': 7, 'Class': 'First'}
dict['Age'] = 8; # update existing entry
dict['School'] = "DPS School" # Add new entry

print ("dict['Age']: ", dict['Age'])
print ("dict['School']: ", dict['School'])

Để biết thêm thông tin, vui lòng tham khảo hướng dẫn này về kiểu dữ liệu từ điển .

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.