variable stringvar propiedades intvar funcion errores control comunes python c++ lua lazy-evaluation

python - stringvar - text variable tkinter



Llame a Python o Lua desde C++ para evaluar una expresión, calculando variables desconocidas solo si es necesario (12)

Tengo una expresión / fórmula como esta

std::string expr="((A>0) && (B>5 || C > 10))";

He investigado un poco y parece que si se conocen los valores A, B, C, al incorporar Lua o Python en un programa C ++, existen funciones eval que pueden sustituir a A, B y C y devolver true o false .

¿Pero qué pasa cuando no conozco todos los valores? Digamos que A es conocida y es -1. Si A es -1, entonces la fórmula se evaluará como "falsa" independientemente de los valores de B o C.

¿Puedo evaluar una fórmula sin conocer todas las variables de antemano? Por ejemplo, si A es 10, tiene sentido buscar el valor de B y volver a evaluar. ¿Cómo podemos resolver estos problemas? Ideas?


Debido al comportamiento de cortocircuito, Python puede evaluar una expresión incluso si no es posible definir todos los valores contenidos. Si no, plantea una excepción:

In [1]: a= False In [3]: a and b Out[3]: False In [4]: a or b NameError: name ''b'' is not defined

Pero la expresión se evalúa de izquierda a derecha:

In [5]: b and a NameError: name ''b'' is not defined


Echaría un vistazo a Sympy u otros sistemas de álgebra computacional Creo que la simplificación algebraica de la evaluación pxeression plus short circuit le permitirá evaluar todos los casos en los que es posible obtener un resultado. Hay algunos casos en los que necesitas saber el valor de alguna variable. Por ejemplo, si tiene una expresión simple como a == b, no va a progresar sin saber el valor de a y b. Sin embargo, algo como (a> = 0) || (a <= 0), la simplificación algebraica resultará en una verdadera suposición de que a no es NAN o algún otro valor que no sea igual a sí mismo.


Entonces, por lo que entiendo de tu pregunta, quieres algo como

if (A>0) { B = getB(); C = getC(); if (B>23 || C==11) explode(); }

es decir, su expresión debe dividirse para que solo trabaje con valores conocidos.


Estoy asumiendo de esta pregunta que

  1. tienes una expresión lógica que depende del resultado de varias funciones;
  2. utiliza los valores de algunas de esas funciones más de una vez (posiblemente antes de evaluar esta expresión, o posiblemente dentro de esta expresión), por lo que desea almacenar sus resultados para evitar llamarlos dos veces; y
  3. le gustaría evaluar la expresión lógica, y en el camino le gustaría recuperar y almacenar valores para funciones que no se han ejecutado anteriormente, pero solo lo suficiente para evaluar la expresión (utilizando el comportamiento normal de cortocircuito).

Mencioné en una respuesta diferente que podría hacer lo mejor para usar el comportamiento de cortocircuito incorporado en C ++. Para hacer eso y lograr el objetivo 2, necesitaría usar funciones en lugar de variables en la expresión lógica. De esa manera puede activar el cálculo de un valor faltante cuando la expresión lo necesite.

A continuación se presentan dos enfoques para hacer eso. El primero envuelve sus funciones lentas con un contenedor de almacenamiento en caché de propósito general. El segundo define un ayudante de almacenamiento en caché personalizado para cada una de sus funciones lentas. Después de compilar, se debe llamar a cualquiera de estos con sus valores A, B y C para la prueba, por ejemplo, evaluate_cached 10 9 -1 . Ambos se comportarán como tú quieras.

evaluate_cached.cpp

# include <stdio.h> # include <stdlib.h> # include <unordered_map> static char **args; // define (slow) functions to calculate each of the needed values int A() { printf("Calculating value for A/n"); return atoi(args[1]); } int B() { printf("Calculating value for B/n"); return atoi(args[2]); } int C() { printf("Calculating value for C/n"); return atoi(args[3]); } typedef int (*int_func)(void); // wrapper to cache results of other functions int cached(int_func func) { // Create an unordered_map to hold function results static std::unordered_map<int_func, int> results; if (results.find(func) == results.end()) { // function hasn''t been called before; call and cache results results[func] = func(); } return results[func]; } int main(int argc, char *argv[]) { if (argc!=4) { fprintf(stderr, "%s must be called with 3 values for A, B and C./n", argv[0]); return 1; } else { args = argv; } // do the evaluation, with short-circuiting if (((cached(A)>0) && (cached(B)>5 || cached(C) > 10))) { printf("condition was true/n"); } else { printf("condition was false/n"); } return 0; }

evaluate_helpers.c

# include <stdio.h> # include <stdlib.h> static char **args; // define (slow) functions to calculate each of the needed values int calculate_A() { printf("Calculating value for A/n"); return atoi(args[1]); } int calculate_B() { printf("Calculating value for B/n"); return atoi(args[2]); } int calculate_C() { printf("Calculating value for C/n"); return atoi(args[3]); } // define functions to retrieve values as needed, // with caching to avoid double-calculation int A() { static int val, set=0; if (!set) val=calculate_A(); return val; } int B() { static int val, set=0; if (!set) val=calculate_B(); return val; } int C() { static int val, set=0; if (!set) val=calculate_B(); return val; } int main(int argc, char *argv[]) { if (argc!=4) { fprintf(stderr, "%s must be called with 3 values for A, B and C./n", argv[0]); return 1; } else { args = argv; } // do the evaluation, with short-circuiting if (((A()>0) && (B()>5 || C() > 10))) { printf("condition was true/n"); } else { printf("condition was false/n"); } return 0; }


He hecho un enfoque "roll-my-own" para esto en el pasado. No es tan difícil para las cosas simples; simplemente creas tus propios objetos que implementan métodos matemáticos mágicos y que rastrean otros objetos.

Si necesita algo más completo, el proyecto sympy está diseñado para hacer matemáticas simbólicas ...


Me parece que la respuesta es sí, sí, puedes intentar evaluar la expresión con información faltante. Deberá definir qué sucede cuando falla una búsqueda de símbolos.

En su caso, necesitará un evaluador de expresiones booleanas y una tabla de símbolos para que el evaluador pueda buscar los símbolos para ejecutar la expresión.

Si logra buscar todos los símbolos, el resultado sería verdadero o falso. Si no puede buscar un símbolo, entonces maneje ese caso, quizás devolviendo Ninguno, nullptr, o generando / lanzando una excepción.

Creo que puedes incrustar el intérprete de python en tu programa c ++ y llamar a una función para evaluar la expresión, lo que es más importante, puedes darle el dictado para usarlo como tabla de símbolos. Si la llamada devuelve un resultado, pudo encontrar suficientes símbolos o accesos directos a un resultado; de lo contrario, generará una excepción que su código c ++ puede detectar.

Puede crear un prototipo de la función en python para evaluar si el enfoque funciona de la manera que desea y luego incrustar.

O puede hacerlo todo en c ++, con una gramática, un lexer, un analizador y un elevador.


No entiendo exactamente lo que quiere hacer o entender, pero estoy de acuerdo con ivan_pozdeev sobre la evaluación de cortocircuito y la evaluación perezosa .

Una expresión booleana se evalúa de izquierda a derecha y, cuando se conoce el resultado, la evaluación se detiene e ignora lo que está a la derecha.

Con Python:

E = "(A > 0) and (B > 5 or C > 10)" A = -1 print(eval(E))

da

False

Pero

E = "(A > 0) and (B > 5 or C > 10)" A = 1 print(eval(E))

da el error " nombre ''B'' no está definido ".


No sé de ninguna biblioteca disponible para manejar esto.

El enfoque habitual sería construir un árbol de expresiones y evaluar lo que es posible, de manera similar al plegamiento constante en compiladores: https://en.wikipedia.org/wiki/Constant_folding

Uno de los aspectos importantes para eso es conocer los valores permitidos para las variables y, por lo tanto, las evaluaciones parciales permitidas, por ejemplo, x*0 (y 0*x ) es 0 si x es un número entero, o número finito de punto flotante, pero no puede ser evaluado si x es un número flotante IEEE (ya que podría ser Nan o infinito), o si x podría ser una matriz ya que [1,1]*0 es [0,0] no el escalar 0 .


Parece que tienes dos desafíos:

  1. Es costoso calcular algunos valores de variables, por lo que debe evitar calcular valores que no son necesarios para evaluar la expresión; y
  2. Su expresión existe como una cadena, compuesta en tiempo de ejecución, por lo que no puede usar la lógica de cortocircuito incorporada de C ++.

Esto significa que necesita alguna forma de evaluar una expresión en tiempo de ejecución, y le gustaría aprovechar la lógica de cortocircuito si es posible. Python podría ser una buena opción para esto, como se muestra en el siguiente ejemplo.

Hay un breve script de Python ( evaluate.py ), que define una función de evaluate() que se puede llamar desde su programa C o C ++. La función evaluate() intentará evaluar la expresión que le das (traduciendo "&&" y "||" a "and" y "and" si es necesario). Si requiere una variable que aún no se ha definido, recuperará un valor para esa variable llamando a una función get_var_value() definida en el programa C / C ++ (y luego almacenará el valor para su uso posterior).

Este enfoque utilizará el comportamiento normal de cortocircuito, por lo que solo solicitará los valores variables necesarios para finalizar la evaluación de la expresión. Tenga en cuenta que esto no reorganizará la expresión para elegir el conjunto mínimo de variables necesarias para evaluarla; sólo utiliza el comportamiento estándar de cortocircuito.

ACTUALIZACIÓN: He agregado un ejemplo al final que define la secuencia de comandos de Python usando un literal de cadena multilínea en el archivo .cpp. Esto podría ser útil si no desea instalar un archivo evalu.py separado junto con su ejecutable. También simplifica un poco la inicialización de Python.

La interacción C / Python en los scripts a continuación se basa en el código en https://docs.python.org/2/extending/embedding.html y https://docs.python.org/2/c-api/arg.html .

Aquí están los archivos:

evaluar.py (script en Python)

# load embedded_methods module defined by the parent C program from embedded_methods import get_var_value # define a custom dictionary class that calls get_var_value(key) for any missing keys. class var_dict(dict): def __missing__(self, var): self[var] = val = get_var_value(var) return val # define a function which can be called by the parent C program def evaluate(expr): # Create a dictionary to use as a namespace for the evaluation (this version # will automatically request missing variables). # Move this line up to the module level to retain values between calls. namespace = var_dict() # convert C-style Boolean operators to Python-style py_expr = expr.replace("||", " or ").replace("&&", " and ").replace(" ", " ") print(''evaluating expression "{}" as "{}"''.format(expr, py_expr)) # evaluate the expression, retrieving variable values as needed return eval(py_expr, namespace)

eval.c (su programa principal; también podría ser eval.cpp, compilado con g ++)

// on Mac, compile with gcc -o evaluate evaluate.c -framework Python #include <Python/Python.h> // Mac // #include <Python.h> // non-Mac? // retain values of argc and argv for equation evaluation int argc; char **argv; /* Calculate the value of a named variable; this is called from the Python script to obtain any values needed to evaluate the expression. */ static PyObject* c_get_var_value(PyObject *self, PyObject *args) { int var_num; char *var_name; char err_string[100]; long var_value; if(!PyArg_ParseTuple(args, "s:get_var_value", &var_name)) { PyErr_SetString(PyExc_ValueError, "Invalid arguments passed to get_var_value()"); return NULL; } // change the code below to define your variable values // This version just assumes A, B, C are given by argv[2], argv[3], argv[4], etc. printf("looking up value of %s: ", var_name); var_num = var_name[0]-''A''; if (strlen(var_name) != 1 || var_num < 0 || var_num >= argc-2) { printf("%s/n", "unknown"); snprintf( err_string, sizeof(err_string), "Value requested for unknown variable /"%s/"", var_name ); PyErr_SetString(PyExc_ValueError, err_string); return NULL; // will raise exception in Python } else { var_value = atoi(argv[2+var_num]); printf("%ld/n", var_value); return Py_BuildValue("l", var_value); } } // list of methods to be added to the "embedded_methods" module static PyMethodDef c_methods[] = { {"get_var_value", c_get_var_value, METH_VARARGS, // could use METH_O "Retrieve the value for the specified variable."}, {NULL, NULL, 0, NULL} // sentinel for end of list }; int main(int ac, char *av[]) { PyObject *p_module, *p_evaluate, *p_args, *p_result; long result; const char* expr; // cache and evaluate arguments argc = ac; argv = av; if (argc < 2) { fprintf( stderr, "Usage: %s /"expr/" A B C .../n" "e.g., %s /"((A>0) && (B>5 || C > 10))/" 10 9 -1/n", argv[0], argv[0] ); return 1; } expr = argv[1]; // initialize Python Py_SetProgramName(argv[0]); Py_Initialize(); // Set system path to include the directory where this executable is stored // (to find evaluate.py later) PySys_SetArgv(argc, argv); // attach custom module with get_var_value() function Py_InitModule("embedded_methods", c_methods); // Load evaluate.py p_module = PyImport_ImportModule("evaluate"); if (PyErr_Occurred()) { PyErr_Print(); } if (p_module == NULL) { fprintf(stderr, "unable to load evaluate.py/n"); return 1; } // get a reference to the evaluate() function p_evaluate = PyObject_GetAttrString(p_module, "evaluate"); if (!(p_evaluate && PyCallable_Check(p_evaluate))) { fprintf(stderr, "Cannot retrieve evaluate() function from evaluate.py module/n"); return 1; } /* Call the Python evaluate() function with the expression to be evaluated. The evaluate() function will call c_get_var_value() to obtain any variable values needed to evaluate the expression. It will use caching and normal logical short-circuiting to reduce the number of requests. */ p_args = Py_BuildValue("(s)", expr); p_result = PyObject_CallObject(p_evaluate, p_args); Py_DECREF(p_args); if (PyErr_Occurred()) { PyErr_Print(); return 1; } result = PyInt_AsLong(p_result); Py_DECREF(p_result); printf("result was %ld/n", result); Py_DECREF(p_evaluate); Py_DECREF(p_module); return 0; }

Resultados:

$ evaluate "((A>0) && (B>5 || C > 10))" -1 9 -1 evaluating expression "((A>0) && (B>5 || C > 10))" as "((A>0) and (B>5 or C > 10))" looking up value of A: -1 result was 0 $ evaluate "((A>0) && (B>5 || C > 10))" 10 9 -1 evaluating expression "((A>0) && (B>5 || C > 10))" as "((A>0) and (B>5 or C > 10))" looking up value of A: 10 looking up value of B: 9 result was 1 $ evaluate "((A>0) && (B>5 || C > 10))" 10 3 -1 evaluating expression "((A>0) && (B>5 || C > 10))" as "((A>0) and (B>5 or C > 10))" looking up value of A: 10 looking up value of B: 3 looking up value of C: -1 result was 0

Como alternativa, puede combinar todo este código en un solo archivo .cpp, como se muestra a continuación. Esto utiliza la capacidad literal de cadena multilínea en C ++ 11.

Autocontenido eval.cpp

// on Mac, compile with g++ evaluate.cpp -o evaluate -std=c++11 -framework Python #include <Python/Python.h> // Mac //#include <Python.h> // non-Mac? /* Python script to be run in embedded interpreter. This defines an evaluate(expr) function which will interpret an expression and return the result. If any variable values are needed, it will call the get_var_values(var) function defined in the parent C++ program */ const char* py_script = R"( # load embedded_methods module defined by the parent C program from embedded_methods import get_var_value # define a custom dictionary class that calls get_var_value(key) for any missing keys. class var_dict(dict): def __missing__(self, var): self[var] = val = get_var_value(var) return val # define a function which can be called by the parent C program def evaluate(expr): # Create a dictionary to use as a namespace for the evaluation (this version # will automatically request missing variables). # Move this line up to the module level to retain values between calls. namespace = var_dict() # convert C-style Boolean operators to Python-style py_expr = expr.replace("||", " or ").replace("&&", " and ").replace(" ", " ") print(''evaluating expression "{}" as "{}"''.format(expr, py_expr)) # evaluate the expression, retrieving variable values as needed return eval(py_expr, namespace) )"; // retain values of argc and argv for equation evaluation int argc; char **argv; /* Calculate the value of a named variable; this is called from the Python script to obtain any values needed to evaluate the expression. */ static PyObject* c_get_var_value(PyObject *self, PyObject *args) { int var_num; char *var_name; char err_string[100]; long var_value; if(!PyArg_ParseTuple(args, "s:get_var_value", &var_name)) { PyErr_SetString(PyExc_ValueError, "Invalid arguments passed to get_var_value()"); return NULL; } // change the code below to define your variable values // This version just assumes A, B, C are given by argv[2], argv[3], argv[4], etc. printf("looking up value of %s: ", var_name); var_num = var_name[0]-''A''; if (strlen(var_name) != 1 || var_num < 0 || var_num >= argc-2) { printf("%s/n", "unknown"); snprintf( err_string, sizeof(err_string), "Value requested for unknown variable /"%s/"", var_name ); PyErr_SetString(PyExc_ValueError, err_string); return NULL; // will raise exception in Python } else { var_value = atoi(argv[2+var_num]); printf("%ld/n", var_value); return Py_BuildValue("l", var_value); } } // list of methods to be added to the "embedded_methods" module static PyMethodDef c_methods[] = { {"get_var_value", c_get_var_value, METH_VARARGS, // could use METH_O "Retrieve the value for the specified variable."}, {NULL, NULL, 0, NULL} // sentinel for end of list }; int main(int ac, char *av[]) { PyObject *p_module, *p_evaluate, *p_args, *p_result; long result; const char* expr; // cache and evaluate arguments argc = ac; argv = av; if (argc < 2) { fprintf( stderr, "Usage: %s /"expr/" A B C .../n" "e.g., %s /"((A>0) && (B>5 || C > 10))/" 10 9 -1/n", argv[0], argv[0] ); return 1; } expr = argv[1]; // initialize Python Py_SetProgramName(argv[0]); Py_Initialize(); // attach custom module with get_var_value() function Py_InitModule("embedded_methods", c_methods); // run script to define evalute() function PyRun_SimpleString(py_script); if (PyErr_Occurred()) { PyErr_Print(); fprintf(stderr, "%s/n", "unable to run Python script"); return 1; } // get a reference to the Python evaluate() function (can be reused later) // (note: PyRun_SimpleString creates objects in the __main__ module) p_module = PyImport_AddModule("__main__"); p_evaluate = PyObject_GetAttrString(p_module, "evaluate"); if (!(p_evaluate && PyCallable_Check(p_evaluate))) { fprintf(stderr, "%s/n", "Cannot retrieve evaluate() function from __main__ module"); return 1; } /* Call the Python evaluate() function with the expression to be evaluated. The evaluate() function will call c_get_var_value() to obtain any variable values needed to evaluate the expression. It will use caching and normal logical short-circuiting to reduce the number of requests. */ p_args = Py_BuildValue("(s)", expr); p_result = PyObject_CallObject(p_evaluate, p_args); Py_DECREF(p_args); if (PyErr_Occurred()) { PyErr_Print(); return 1; } result = PyInt_AsLong(p_result); Py_DECREF(p_result); printf("result was %ld/n", result); Py_DECREF(p_module); Py_DECREF(p_evaluate); return 0; }


Puedes hacerlo así:

class LazyValues(): def __init__(self): self._known_values = {} def __getitem__(self, var): try: return self._known_values[var] except KeyError: print("Evaluating %s..." % var) return self._known_values.setdefault(var, eval(var)) def lazy_eval(expr, lazy_vars): for var in lazy_vars: expr = expr.replace(var, "lazy_values[''%s'']" % var) # will look like ((lazy_value[''A'']>0) && (lazy_value[''B'']>5 || lazy_value[''C''] > 10)) lazy_values = LazyValues() return eval(expr) lazy_eval("((A>0) and (B>5 or C > 10))", lazy_vars=[''A'', ''B'', ''C'']) # Evaluating A... # .... # NameError: name ''A'' is not defined A = -1 lazy_eval("((A>0) and (B>5 or C > 10))", lazy_vars=[''A'', ''B'', ''C'']) #Evaluating A... #False A = 5 B = 6 lazy_eval("((A>0) and (B>5 or C > 10))", lazy_vars=[''A'', ''B'', ''C'']) # Evaluating A... # Evaluating B... # True

Más detalles más adelante ...


Si bien esta es una implementación muy cruda de su solución, pero se adapta perfectamente a su situación, aunque utiliza mucho, if else y el manejo de excepciones.

def main_func(): def checker(a, b=None, c=None): if a is None: del a if b is None: del b if c is None: del c d = eval(''a>0 and (b>5 or c>10)'') return d return checker def doer(a=None, b=None, c=None): try: return main_func()(a,b,c) except NameError as e: if "''a'' is not" in str(e): return ''a'' elif "''b'' is not" in str(e): return ''b'' elif "''c'' is not" in str(e): return ''c'' def check_ret(ret): return type(ret) == bool def actual_evaluator(): getter = { "a": get_a, "b": get_b, "c": get_c } args = [] while True: ret = doer(*tuple(args)) if not check_ret(ret): val = getter[ret]() args.append(val) else: return ret if __name__ == ''__main__'': print actual_evaluator()

Ahora, explicando mi código, main_func devuelve otra función que se usa para evaluar la expresión dada en una cadena. Mientras que aquí la cadena ha sido codificada, siempre puede pasarla como parámetro a la función y reemplazar la cadena dentro de eval con el parámetro.

En doer , la función devuelta por main_func se invoca y si se lanza un NameError , lo que sucede en caso de que las condiciones anteriores sean falsas y se calculen nuevos valores, devuelve la variable específica que debe calcularse. Todo esto se verifica en el actual_evaluator donde se actual_evaluator los valores de las variables a través de la función get_variable_name que puede definir en su get_variable_name . En mi código, usé números aleatorios para verificar la validez, pero como usted dijo que tendría que evaluar las diversas variables por otros medios para poder llamar a las funciones respectivas.


Una forma es analizar la expresión en un árbol y evaluar el árbol. Las subexpresiones para las que se conocen todas las variables serán evaluadas completamente. El efecto será simplificar el árbol.

En su ejemplo, el árbol tiene && en la parte superior con dos subárboles, el izquierdo es el árbol para A>0 . Para evaluar el árbol, evaluamos el subárbol izquierdo, que devuelve -1, por lo que no necesitamos evaluar el subárbol correcto, porque el operador es && . Todo el árbol se evalúa como falso .