Я знаю, как работает сравнение функций в 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
имеют одну и ту же функцию сортировки перед выполнением объединения и т.д. на них.
Именованные функции менее интересны, потому что я могу предположить, что они будут разными, если они не идентичны. В конце концов, предположим, что кто-то создал два сортировки с именованной функцией в аргументе 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
будут оцениваться как равные?
Если промежуточный код вообще содержит любой вызов функции, значение fields
может быть изменено, что приведет к различным ключевым функциям для s1
и s2
. Поскольку мы явно не будем проводить анализ потока управления для решения этой проблемы, ясно, что мы должны оценивать эти две лямбда-функции как разные, если мы пытаемся выполнить эту оценку до выполнения. (Даже если fields
не был глобальным, у него могло быть другое имя, связанное с ним и т.д.). Это серьезно сократило бы полезность всего этого упражнения, так как мало лямбда-функций не зависели бы от среды.
ИЗМЕНИТЬ 2:
Я понял, что очень важно сравнивать объекты функции, как они существуют во время выполнения. Без этого нельзя сравнивать все функции, зависящие от переменных из внешней области; и большинство полезных функций имеют такие зависимости. Рассматриваемые во время выполнения все функции с одной и той же сигнатурой сравнимы по чистому, логичному пути, независимо от того, на что они зависят, являются ли они нечистыми и т.д.
В результате мне нужен не только байт-код, но и глобальное состояние с момента создания функционального объекта (предположительно __globals__
). Затем мне нужно сопоставить все переменные из внешней области с значениями из __globals__
.