¿Cómo se hace una evaluación de python solo dentro de un contexto de objeto?

¿Es posible hacer algo como?

c = MyObj() c.eval("func1(42)+func2(24)") 

en Python … es decir, func1 () y func2 () deben evaluarse dentro del scope del objeto ‘c’ (si fueran funciones miembro dentro de esa definición de clase)? No puedo hacer un simple análisis, ya que para mi aplicación, las cadenas de evaluación pueden complicarse arbitrariamente. Supongo que hacer algo de magia con el módulo ast podría hacer el truco, pero debido a la escasez de literatura sobre ast, no estoy seguro de dónde mirar:

 import ast class MyTransformer(ast.NodeTransformer): def visit_Name(self, node): # do a generic_visit so that child nodes are processed ast.NodeVisitor.generic_visit(self, node) return ast.copy_location( # do something magical with names that are functions, so that they become # method calls to a Formula object newnode, node ) class Formula(object): def LEFT(self, s, n): return s[:n] def RIGHT(self, s, n): return s[0-n:] def CONCAT(self, *args, **kwargs): return ''.join([arg for arg in args]) def main(): evalString = "CONCAT(LEFT('Hello', 2), RIGHT('World', 3))" # we want to emulate something like Formula().eval(evalString) node = ast.parse(evalString, mode='eval') MyTransformer().visit(node) ast.fix_missing_locations(node) print eval(compile(node, '', mode='eval')) 

Es casi seguro que no quieres hacer esto, pero puedes.

El contexto para eval es los diccionarios globales y locales en los que desea evaluar su código. Los casos más comunes son probablemente eval(expr, globals(), mycontext) y eval(expr, mycontext) , que reemplazan a los predeterminados local y global Contextos, respectivamente, dejando al otro solo. Reemplazar el contexto local con el diccionario de un objeto es similar a ejecutar “dentro” (un método de) ese objeto, aunque tenga en cuenta que “ser una función miembro” no es tan bueno como podría esperar si no lo hace. tener un self para llamar a otras funciones miembro en …

De todos modos, aquí hay un ejemplo rápido:

 >>> class Foo(object): ... def __init__(self): ... self.bar = 3 >>> foo = Foo() >>> eval('a', globals(), foo.__dict__) 3 

Tenga en cuenta que __dict__ puede no ser exactamente lo que quiere aquí. Por ejemplo:

 >>> class Foo(object): ... @staticmethod ... def bar(): ... return 3 >>> foo = Foo() >>> eval('bar()', globals(), foo.__dict__) NameError: name 'bar' is not defined >>> eval('bar()', globals(), {k: getattr(foo, k) for k in dir(foo)} 3 

Para hacer que esto funcione de la manera que usted desea, debe saber exactamente cómo definir lo que quiere, en términos de Python, lo que requiere saber un poco sobre cómo funcionan los objetos bajo las coberturas (MRO, tal vez descriptores, etc.).

Si realmente necesita una eval y realmente necesita proporcionar contextos arbitrarios, es probable que esté mejor desarrollando esos contextos explícitamente (como diccionarios) en lugar de intentar forzar a los objetos a ese rol:

 >>> foo = { ... 'bar': lambda: 3 ... } >>> eval('bar()', globals(), foo) 

Este uso está mucho más cerca del estilo de Javascript que intentas emular en Python de todos modos.

Por supuesto, a diferencia de JS, Python no te permite poner definiciones de varias líneas dentro de una expresión, por lo que para casos complejos debes hacer esto:

 >>> def bar(): ... return 3 >>> foo = { ... 'bar': bar ... } >>> eval('bar()', globals(), foo) 

Pero podría decirse que casi siempre es más legible (que es básicamente el argumento detrás de Python que no permite definiciones de líneas múltiples en expresiones).

Entonces, te aconsejo que hagas algo como esto:

 >>> class S(object): ... def add(self, a, b): ... return a + b ... >>> filter(lambda (k,v): not k.startswith("__"), S.__dict__.items()) [('add', )] >>> target = S() >>> map(lambda (k, f): (k, f.__get__(target, S)), filter(lambda (k,v): not k.startswith("__"), S.__dict__.items())) [('add', >)] >>> dict(_) {'add': >} >>> eval("add(45, 10) + add(10, 1)", _, {}) 66 

Parece que lo que necesitas. Déjame explicarte cómo funciona esto.

  1. eval acepta locales y globales como parámetros.
  2. Por lo tanto, debemos definir un contexto global especial que será “representación” de su clase.
  3. Para hacer esto, necesitamos proporcionar un diccionario globals de todos los métodos limitados “valiosos”.
  4. Partiendo de parte simple. Tenemos definición de clase S ¿Cómo obtener todos los métodos “valiosos”? filter simples de filter nombres de S.__dict__ para verificar si el nombre del método comienza con __ o no (usted ve, que como resultado obtenemos la lista con 1 elemento – add función).
  5. Crear target = instancia de la clase S que será “eval context”.
  6. El siguiente paso es el más “complicado”. Necesitamos crear un “método enlazado” de cada función. Para hacer esto, usamos el hecho de que la clase __dict__ almacena funciones, cada función no es descriptora de datos y el método delimitado se puede buscar simplemente con func.__get__(obj, type(obj)) . Esta operación se realiza en el map .
  7. Tomar el resultado del paso anterior, crear dict de él.
  8. Pasar como globals a la función eval .

Espero que esto sea de ayuda.

La solución propuesta anteriormente para poblar locals funciona bien en la mayoría de los casos, pero puede ser problemática en el caso de las propiedades (descriptores de datos). Estos se evalúan una vez cuando se llena el diccionario. Esto significa que múltiples referencias al mismo nombre de variable siempre devolverán la misma instancia exacta, lo que puede no ser el comportamiento esperado en el caso de las propiedades.

Este problema se puede resolver observando que eval espera un argumento locals que se comporte como un dict (a diferencia de los globales, que debe ser un dict ). En otras palabras, podemos anular __getitem__ en su instancia para resolver nombres de variables sobre la marcha en el contexto de la instancia y pasarlo directamente como atributo locals a eval . Su ejemplo puede ser implementado como:

 class Formula(object): def __getitem__(self, key): if key not in dir(self) or key.startswith('__'): raise KeyError(key) return getattr(self, key) def LEFT(self, s, n): return s[:n] def RIGHT(self, s, n): return s[0-n:] def CONCAT(self, *args, **kwargs): return ''.join([arg for arg in args]) def main(): evalString = "CONCAT(LEFT('Hello', 2), RIGHT('World', 3))" print eval(evalString, {}, Formula()) if __name__ == "__main__": main() 

Este truco debería funcionar con herencia, métodos estáticos, métodos de clase y propiedades. Finalmente, el uso de dir y getattr evita la necesidad de interactuar directamente con __dict__ o __mro__ , aunque los resultados de dir no siempre están completos .

Puede consultar la respuesta aceptada a esta pregunta: “Obtención del bloque de comandos que se ejecutarán en la instrucción with” .

Esta ha sido una forma útil para que yo creara mis propios contextos en los que las operaciones matemáticas en matrices rectangulares, como los marcos de datos de Python Pandas, “simplemente funcionan” sin tener que preocuparme por la fea syntax de Pandas. Por ejemplo, cuando escribo ” a = x*y ” dentro del contexto, automáticamente asigna un atributo al objeto de contexto, y sabe realizar operaciones vectoriales con los atributos x e y del objeto de contexto.

He encontrado que este contexto es muy útil, a pesar del hecho de que cada vez que pregunto en StackOverflow, a menudo recibo respuestas falsas que no deben ser lo que realmente quiero hacer.

Probablemente podría hacer que esto funcione para el contexto en el que eval busca funciones.