EDIT : Якщо всі ваші клавіші - це рядки , то перш ніж продовжувати читати цю відповідь, будь ласка, знайдіть значно простіше (і швидше) рішення Джека О'Коннора (яке також працює для хешування вкладених словників).
Незважаючи на те, що відповідь прийнято, назва питання "Заховання словника пітона", і відповідь неповна щодо цього заголовка. (Що стосується суті питання, відповідь є повною.)
Вкладені словники
Якщо ви шукаєте переповнення стека для того, як хеш-словник, можна наткнутися на це влучно під назвою питання і залишити незадоволеним, якщо намагається хеш помножити вкладені словники. Відповідь вище не працюватиме в цьому випадку, і вам доведеться реалізувати якийсь рекурсивний механізм, щоб отримати хеш.
Ось один з таких механізмів:
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()))))
Бонус: Захоплення предметів та класів
Ця hash()
функція чудово працює, коли у вас є хеш-класи або екземпляри. Однак ось одне питання, яке я знайшов із хешем щодо об’єктів:
class Foo(object): pass
foo = Foo()
print (hash(foo)) # 1209812346789
foo.a = 1
print (hash(foo)) # 1209812346789
Хеш - той самий, навіть після того, як я змінив foo. Це тому, що особа foo не змінилася, тому хеш - той самий. Якщо ви хочете, щоб Foo хешував по-різному, залежно від його поточного визначення, рішення - відмовитися від того, що насправді змінюється. У цьому випадку __dict__
атрибут:
class Foo(object): pass
foo = Foo()
print (make_hash(foo.__dict__)) # 1209812346789
foo.a = 1
print (make_hash(foo.__dict__)) # -78956430974785
На жаль, коли ви намагаєтесь зробити те ж саме з самим класом:
print (make_hash(Foo.__dict__)) # TypeError: unhashable type: 'dict_proxy'
__dict__
Властивість класу не є звичайним словником:
print (type(Foo.__dict__)) # type <'dict_proxy'>
Ось аналогічний механізм, як і попередній, який буде відповідним чином обробляти класи
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()))))
Ви можете використовувати це для повернення хеш-коду з усіх бажаючих елементів:
# -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__]))
ПРИМІТКА: всі наведені вище коди передбачають Python 3.x. Не тестував у попередніх версіях, хоча я припускаю, що make_hash()
буде працювати у скажімо, 2.7.2. Що стосується того, щоб приклади працювали, я це знаю
func.__code__
слід замінити на
func.func_code