Я знаю, как работает сравнение функций в Python 3 (просто сравнивая адрес в памяти), и понимаю почему.
Я также понимаю, что «настоящее» сравнение (возвращают ли функции f
и g
один и тот же результат при одних и тех же аргументах для любых аргументов?) практически невозможно.
Я смотрю для чего-то среднего.Я хочу, чтобы сравнение работало в самых простых случаях идентичных функций и, возможно, в некоторых менее тривиальных:
lambda x : x == lambda x : x # True
lambda x : 2 * x == lambda y : 2 * y # True
lambda x : 2 * x == lambda x : x * 2 # True or False is fine, but must be stable
lambda x : 2 * x == lambda x : x + x # True or False is fine, but must be stable
Обратите внимание, что я заинтересован в решении этой проблемы для анонимных функций ( lambda
), но не стал бы не возражаете, если решение также работает для именованных функций.
Мотивация для этого заключается в том, что внутри модуля blist
было бы неплохо проверить, что два экземпляра sortedset
имеют одинаковую функцию сортировки перед выполнением объединения и т. д. на них.
Именованные функции представляют меньший интерес, поскольку я могу предположить, что они различны, когда они не идентичны. В конце концов, предположим, что кто-то создал два sortedsets с именованной функцией в аргументе key
. Если они намерены сделать эти экземпляры «совместимыми» для операций над множествами, они, вероятно, будут использовать одну и ту же функцию, а не две отдельные именованные функции, выполняющие идентичные операции.
Я могу придумать только три подхода. Все они кажутся сложными, поэтому любые идеи приветствуются.
Сравнение байт-кодов может работать, но может раздражать, что оно зависит от реализации (и, следовательно, код, который работал на одном Python, ломается на другом).
Сравнение токенизированного исходного кода кажется разумным и переносимым. Конечно, он менее мощный (поскольку одинаковые функции чаще отвергаются).
Надежная эвристика, заимствованная из какого-нибудь учебника по символьным вычислениям, теоретически является лучшим подходом. Это может показаться слишком тяжелым для моей цели, но на самом деле это может подойти, поскольку лямбда-функции обычно крошечные, и поэтому они будут работать быстро.
РЕДАКТИРОВАТЬ
Более сложный пример, основанный на комментарии @delnan:
# global variable
fields = ['id', 'name']
def my_function():
global fields
s1 = sortedset(key = lambda x : x[fields[0].lower()])
# some intervening code here
# ...
s2 = sortedset(key = lambda x : x[fields[0].lower()])
Можно ли ожидать, что ключевые функции для s1
и s2
оцениваются как равные?
Если промежуточный код вообще содержит какой-либо вызов функции, значение полей может быть изменено, что приведет к различным функциям клавиш для
s1
и s2
. Поскольку мы явно не будем проводить анализ потока управления для решения этой проблемы, ясно, что мы должны оценивать эти две лямбда-функции как разные, если мы пытаемся выполнить эту оценку перед выполнением. (Даже если бы fields
не было глобальным, к нему могло бы быть привязано другое имя и т. д.) Это сильно урезало бы полезность всего этого упражнения, поскольку немногие лямбда-функции не зависели бы от окружающая обстановка.
РЕДАКТИРОВАТЬ 2:
Я понял, что очень важно сравнивать объекты функций, поскольку они существуют во время выполнения. Без этого нельзя сравнивать все функции, которые зависят от переменных из внешней области видимости; и большинство полезных функций имеют такие зависимости. Рассматриваемые во время выполнения, все функции с одной и той же сигнатурой сравнимы чистым, логичным образом, вне зависимости от того, от чего они зависят, являются ли они нечистыми и т.д.
В результате мне нужен не только байт-код, но и глобальный состояние на момент создания объекта функции (предположительно __globals__
). Затем я должен сопоставить все переменные из внешней области со значениями из __globals__
.