EDIT : Nếu tất cả các khóa của bạn là chuỗi , thì trước khi tiếp tục đọc câu trả lời này, vui lòng xem giải pháp đơn giản hơn (và nhanh hơn) của Jack O'Connor (cũng hoạt động để băm từ điển lồng nhau).
Mặc dù câu trả lời đã được chấp nhận, tiêu đề của câu hỏi là "Băm một từ điển trăn" và câu trả lời không đầy đủ về tiêu đề đó. (Liên quan đến phần chính của câu hỏi, câu trả lời đã hoàn tất.)
Từ điển lồng nhau
Nếu một người tìm kiếm Stack Overflow để tìm cách băm từ điển, người ta có thể vấp phải câu hỏi có tiêu đề thích hợp này và không hài lòng nếu ai đó cố gắng băm nhiều từ điển lồng nhau. Câu trả lời ở trên sẽ không hoạt động trong trường hợp này và bạn sẽ phải thực hiện một số loại cơ chế đệ quy để truy xuất hàm băm.
Đây là một cơ chế như vậy:
import copy
def make_hash(o):
"""
Makes a hash from a dictionary, list, tuple or set to any level, that contains
only other hashable types (including any lists, tuples, sets, and
dictionaries).
"""
if isinstance(o, (set, tuple, list)):
return tuple([make_hash(e) for e in o])
elif not isinstance(o, dict):
return hash(o)
new_o = copy.deepcopy(o)
for k, v in new_o.items():
new_o[k] = make_hash(v)
return hash(tuple(frozenset(sorted(new_o.items()))))
Phần thưởng: Các đối tượng và lớp băm
Các hash()
chức năng hoạt động tuyệt vời khi bạn băm lớp hoặc trường hợp. Tuy nhiên, đây là một vấn đề tôi tìm thấy với hàm băm, liên quan đến các đối tượng:
class Foo(object): pass
foo = Foo()
print (hash(foo)) # 1209812346789
foo.a = 1
print (hash(foo)) # 1209812346789
Băm là như nhau, ngay cả sau khi tôi đã thay đổi foo. Điều này là do danh tính của foo chưa thay đổi, vì vậy hàm băm là như nhau. Nếu bạn muốn foo băm khác nhau tùy thuộc vào định nghĩa hiện tại của nó, giải pháp là băm ra bất cứ điều gì thực sự thay đổi. Trong trường hợp này, __dict__
thuộc tính:
class Foo(object): pass
foo = Foo()
print (make_hash(foo.__dict__)) # 1209812346789
foo.a = 1
print (make_hash(foo.__dict__)) # -78956430974785
Than ôi, khi bạn cố gắng làm điều tương tự với chính lớp đó:
print (make_hash(Foo.__dict__)) # TypeError: unhashable type: 'dict_proxy'
__dict__
Thuộc tính lớp không phải là một từ điển bình thường:
print (type(Foo.__dict__)) # type <'dict_proxy'>
Đây là một cơ chế tương tự như trước đây sẽ xử lý các lớp một cách thích hợp:
import copy
DictProxyType = type(object.__dict__)
def make_hash(o):
"""
Makes a hash from a dictionary, list, tuple or set to any level, that
contains only other hashable types (including any lists, tuples, sets, and
dictionaries). In the case where other kinds of objects (like classes) need
to be hashed, pass in a collection of object attributes that are pertinent.
For example, a class can be hashed in this fashion:
make_hash([cls.__dict__, cls.__name__])
A function can be hashed like so:
make_hash([fn.__dict__, fn.__code__])
"""
if type(o) == DictProxyType:
o2 = {}
for k, v in o.items():
if not k.startswith("__"):
o2[k] = v
o = o2
if isinstance(o, (set, tuple, list)):
return tuple([make_hash(e) for e in o])
elif not isinstance(o, dict):
return hash(o)
new_o = copy.deepcopy(o)
for k, v in new_o.items():
new_o[k] = make_hash(v)
return hash(tuple(frozenset(sorted(new_o.items()))))
Bạn có thể sử dụng điều này để trả về một bộ băm gồm nhiều yếu tố bạn muốn:
# -7666086133114527897
print (make_hash(func.__code__))
# (-7666086133114527897, 3527539)
print (make_hash([func.__code__, func.__dict__]))
# (-7666086133114527897, 3527539, -509551383349783210)
print (make_hash([func.__code__, func.__dict__, func.__name__]))
LƯU Ý: tất cả các mã trên giả định Python 3.x. Không thử nghiệm trong các phiên bản trước, mặc dù tôi giả sử make_hash()
sẽ hoạt động trong 2.7.2. Theo như làm công việc ví dụ, tôi làm biết rằng
func.__code__
nên được thay thế bằng
func.func_code