Sé cómo funciona la comparación de funciones en Python 3 (simplemente comparando la dirección en la memoria), y entiendo por qué.Desarrollar una heurística para probar funciones anónimas simples de Python para la equivalencia
También entiendo que la comparación "verdadera" (¿las funciones f
y g
devuelven el mismo resultado dados los mismos argumentos, para cualquier argumento?) Es prácticamente imposible.
Estoy buscando algo intermedio. Quiero la comparación para trabajar en los casos más simples de funciones idénticas, y, posiblemente, algunos menos triviales:
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
Tenga en cuenta que estoy interesado en la solución de este problema para funciones anónimas (lambda
), pero no le importaría si la solución también funciona para funciones nombradas.
La motivación para esto es que dentro del módulo blist
, sería bueno verificar que dos instancias sortedset
tengan la misma función de clasificación antes de realizar una unión, etc. en ellas.
Las funciones nombradas son de menor interés porque puedo asumir que son diferentes cuando no son idénticas. Después de todo, supongamos que alguien creó dos conjuntos ordenados con una función nombrada en el argumento key
. Si pretenden que estas instancias sean "compatibles" para los propósitos de las operaciones de conjunto, probablemente usarían la misma función, en lugar de dos funciones nombradas separadas que realizan operaciones idénticas.
Solo puedo pensar en tres enfoques. Todos parecen difíciles, por lo que cualquier idea es apreciada.
bytecodes Comparando podrían funcionar pero podría ser molesto que es dependiente de la implementación (y por lo tanto el código que trabajó en uno de Python rompe en otro).
Comparar el código fuente tokenizado parece razonable y portátil. Por supuesto, es menos potente (ya que es más probable que se rechacen funciones idénticas).
Una heurística sólida tomada de un libro de texto simbólico de cálculo es teóricamente el mejor enfoque. Puede parecer demasiado pesado para mi propósito, pero en realidad podría ser una buena opción, ya que las funciones de lambda suelen ser muy pequeñas, por lo que funcionarían rápidamente.
EDITAR
Un ejemplo más complicado, basado en el comentario de @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()])
qué esperaría las funciones clave para s1
y s2
para evaluar como iguales?
Si el código de intervenir contiene cualquier llamada de función en absoluto, el valor de fields
se pueden modificar, lo que resulta en diferentes funciones de las teclas para s1
y s2
. Como claramente no haremos un análisis de flujo de control para resolver este problema, está claro que tenemos que evaluar estas dos funciones lambda como diferentes, si estamos tratando de realizar esta evaluación antes del tiempo de ejecución. (Incluso si fields
no fuera global, podría tener otro nombre vinculado a él, etc.) Esto reduciría drásticamente la utilidad de todo este ejercicio, ya que pocas funciones lambda no tendrían ninguna dependencia del entorno.
EDIT 2:
me di cuenta de que es muy importante comparar los objetos de función tal como existen en tiempo de ejecución. Sin eso, no se pueden comparar todas las funciones que dependen de variables del alcance externo; y las funciones más útiles sí tienen tales dependencias. Considerado en tiempo de ejecución, todas las funciones con la misma firma son comparables de forma limpia y lógica, independientemente de en qué dependan, si son impuras, etc.
Como resultado, no solo necesito el bytecode sino también el estado global a partir del momento en que se creó el objeto de función (presumiblemente __globals__
). Entonces tengo que unir todas las variables desde el alcance exterior a los valores de __globals__
.
¿Sabe usted, y dio cuenta del hecho, que el significado de un objeto de función puede cambiar drásticamente dependiendo de qué entorno se cierra? Por ejemplo, dos objetos de función creados a partir de 'lambda: 1/0 if foo else 1' variarán enormemente en su comportamiento si uno cierra' foo = True' y el otro cierra 'foo = False'. Y ni siquiera pensemos en las funciones impuras y 'eval' ... – delnan
@delnan ¡Gracias, no pensé en esto! Estoy actualizando la pregunta. – max
Para abordar su problema real, no necesita estrictamente lo que está pidiendo. Solo te importa si dos lambdas son * de tipo equivalente *. ¿Cómo se ven tus datos? Podrías darles una muestra, pero no es 100% confiable. Yo diría que lo mejor para la solución (para su problema real) sería un mejor diseño. –