Позвольте мне объяснить, зачем мне это нужно:
Я разрабатываю библиотеку и нуждаюсь в сериализации функций python.
Вот две служебные функции, которые я использую:
def serialize_func(fn: function) -> Tuple[bytes, str]:
return marshal.dumps(fn.__code__), fn.__name__
def deserialize_func(serialized_fn: Tuple[bytes, str]) -> function:
return types.FunctionType(
marshal.loads(serialized_fn[0]), globals(), serialized_fn[1]
)
Теперь, если каким-то образом я могу кэшировать сериализованные функции и хранить их в таком dict
:
Dict[function_hash, function]
{
<function hash>: <function>,
<function hash>: <function>,
...
}
Это приведет к значительному улучшению производительности, поскольку мне не нужно многократно сериализовывать (и переносить) их.
Есть ли надежный способ достичь этого?
EDIT: Если я попробовал хэшировать функцию, это не совсем то, что я имею в виду.
In [1]: def x():
...: pass
In [2]: hash(x)
Out[2]: 8745212393041
In [3]: def x():
...: pass
In [4]: hash(x)
Out[4]: -9223363291642382793
Мне нужно 2 функции с одним и тем же телом, сигнатурой, глобальной областью (и чем-то другим, чего я, вероятно, не хватает), чтобы вернуть тот же хеш.
Вот библиотека, о которой идет речь.
EDIT: Вот несколько подробных объяснений, почему я хочу это сделать.
Я отправляю функцию из процесса ("Клиент") на другой ("Сервер") и выполняю его на "Сервер".
Звучит странно и сложно, но у меня есть веская причина.
Весь этот процесс делает функцию строго атомной для всех "клиентов"
"Сервер" - это "Актер". Таким образом, одна и только одна функция может работать одновременно, что облегчает программисту избегать условий гонки.
Теперь, если "Клиент" уже отправил функцию на "Сервер" один раз, а затем для последующих взаимодействий, он отправляет только хэш этой функции, а "Сервер" может просто искать эту функцию в своей таблице.
Мне нужно 2 функции с одним и тем же телом, сигнатурой, глобальной областью (и чем-то другим, чего я, вероятно, не хватает), чтобы вернуть тот же хеш.
Тогда вам нужно будет построить хэш самостоятельно, исходя из этих критериев.
Например:
def foo(x):
return "It is " + str(x + 1)
def bar(x):
return "The answer is " + str(x + 2)
def quux(x):
return "The answer is " + str(x - 2)
def fnhash(f):
c = f.__code__
return hash((c.co_argcount, c.co_code))
fnhash(foo)
# => -640999299468968616
fnhash(bar)
# => -640999299468968616
fnhash(quux)
# => -1235803056671018747
Обратите внимание, что первые одни и те же, потому что код и число позиционных параметров одинаковы - мы не включили co_consts
, поэтому другая строка и изменение с 1
по 2
не учитываются. Обратите внимание, что последний отличается, потому что мы изменили операцию (которая повлияла на co_code
, что в свою очередь повлияло на fnhash
).
Вам будет нужно выбрать именно те атрибуты объекта кода, которые вы выберете, чтобы иметь смысл (например, я сомневаюсь, что вы захотите включить co_filename
и co_firstline
). См. Значения различных полей co_*
в inspect
документах.
marshal.dumps
), поэтому они работают без каких-либо изменений в качестве ключей словаря. Хотя столкновение с хеш-значением встречается не часто, это вполне возможно, поэтому не рекомендуется использовать словарь, в котором ключом является хеш-значение.