2016-04-01 14 views
6

Si consideri che abbiamo la seguente ingressoConvert formula personalizzata alla funzione python

formula = "(([foo] + [bar]) - ([baz]/2))" 

function_mapping = { 
        "foo" : FooFunction, 
        "bar" : BarFunction, 
        "baz" : BazFunction, 
        } 

C'è qualche libreria Python che mi permette di analizzare la formula e convertirlo in una funzione di rappresentanza pitone.

es.

converted_formula = ((FooFunction() + BarFunction() - (BazFunction()/2)) 

Attualmente sto cercando in qualcosa di simile

In [11]: ast = compiler.parse(formula) 

In [12]: ast 
Out[12]: Module(None, Stmt([Discard(Sub((Add((List([Name('foo')]), List([Name('bar')]))), Div((List([Name('baz')]), Const(2))))))])) 

e poi elaborare ulteriormente questo albero ast.

Sei a conoscenza di una soluzione alternativa più pulita? Qualsiasi aiuto o intuizione è molto apprezzata!

+0

Un problema potenziale con l'utilizzo di 'compiler.parse()' è che analizza in base alla sintassi di Python, che è il motivo per cui si è scoperto il '[ foo] 'nella formula in' Elenco ([Nome ('foo')]) '. Qual è la sintassi utilizzata nelle formule? – martineau

+0

@martineau Vero, c'è un problema con la struttura delle formule sicne che si scontra con il tipo Elenco python. Definisco la sintassi delle formule, quindi posso dare qualcosa come 'In [18]: formula =" ((foo + bar) - (baz/2)) " In [19]: ast = compiler.parse (formula) In [20]: ast Out [20]: Modulo (Nessuno, Stmt ([Discard (Sub ((Aggiungi ((Nome ('foo'), Nome ('bar'))), Div ((Nome ('baz'), Const (2))))))])) ' – alchemist111

+0

Potresti essere in grado di eludere il problema della sintassi con' compiler.parse() 'con" semplicemente "facendo la sostituzione del testo come ho mostrato nella mia [risposta] (http://stackoverflow.com/a/36354572/355230) di seguito. Detto questo, potrebbe essere meglio definire la sintassi delle espressioni della formula in modo che non sia in conflitto con la sintassi Python esistente. Ad esempio, invece del modulo 're', potrebbe essere possibile fare ciò che vuoi usando [' string.Template'] (https://docs.python.org/2/library/string.html#string.Template .template) sostituzione '$' sintassi che potrebbe essere più facile da capire e implementare. – martineau

risposta

2

È possibile utilizzare il modulo re per fare ciò che si desidera tramite la corrispondenza del modello di espressione regolare e la sostituzione del testo relativamente semplice.

import re 

alias_pattern = re.compile(r'''(?:\[(\w+)\])''') 

def mapper(mat): 
    func_alias = mat.group(1) 
    function = function_alias_mapping.get(func_alias) 
    if not function: 
     raise NameError(func_alias) 
    return function.__name__ + '()' 

# must be defined before anything can be mapped to them 
def FooFunction(): return 15 
def BarFunction(): return 30 
def BazFunction(): return 6 

function_alias_mapping = dict(foo=FooFunction, bar=BarFunction, baz=BazFunction) 
formula = "(([foo] + [bar]) - ([baz]/2))" 

converted_formula = re.sub(alias_pattern, mapper, formula) 
print('converted_formula = "{}"'.format(converted_formula)) 

# define contexts and function in which to evalute the formula expression 
global_context = dict(FooFunction=FooFunction, 
         BarFunction=BarFunction, 
         BazFunction=BazFunction) 
local_context = {'__builtins__': None} 

function = lambda: eval(converted_formula, global_context, local_context) 
print('answer = {}'.format(function())) # call function 

uscita:

converted_formula = "((FooFunction() + BarFunction()) - (BazFunction()/2))" 
answer = 42 
+0

Funziona molto bene! Grazie! Mi stavo allontanando dall'usare "eval" poiché ero preoccupato per le implicazioni sulla sicurezza. Fintanto che convaliderò l'input a fondo, pensi che questo sarebbe un caso d'uso valido per 'eval'? – alchemist111

+0

Cosa intendi "abbastanza bene"? 'eval' è OK se si adottano alcune precauzioni - vedere la risposta aggiornata. – martineau

+1

Penso che volessi dire "Funziona perfettamente!" :) Grazie per l'aggiornamento! Mi assicurerò di prendere le precauzioni necessarie prima di usare eval. – alchemist111

0

È possibile utilizzare la cosiddetta formattazione di stringhe per ottenere ciò.

function_mapping = { 
        "foo" : FooFunction(), 
        "bar" : BarFunction(), 
        "baz" : BazFunction(), 
        } 

formula = "(({foo} + {bar}) - ({baz}/2))".format(**function_mapping) 

vi darà il risultato di ((FooFunction() + BarFunction() - (BazFunction()/2))

Ma credo che le funzioni eseguiranno quando viene caricato il modulo, quindi forse una soluzione migliore sarebbe

Questo vi darà la stringa '((FooFunction() + BarFunction() - (BazFunction()/2))' che è possibile eseguire in qualsiasi momento con la funzione eval.

+0

Le parentesi di funzioni sono mancanti nel secondo esempio. Altri dettagli: se una funzione è presente più volte nella formula, verrà chiamata più volte. [LRU] (https://docs.python.org/3/library/functools.html#functools.lru_cache), il flag dirty o il caching possono essere utili qui. – aluriak

+0

@aluriak Non sapevo che esistesse un modo integrato per memorizzare automaticamente le funzioni per te. È davvero fantastico. E ho aggiornato la mia risposta per includere anche la parentesi. Grazie per la segnalazione. –

+2

"Questo ti darà la stringa '((FooFunction() + BarFunction() - (BazFunction()/2))'", in realtà no .... questo è completamente sbagliato .... – Netwave

0

Se si modifica la sintassi utilizzata nelle formule un po ', (un altro) modo per fare questo - come ho già detto in un comment - sarebbe quella di utilizzare string.Template sostituzione.

Per curiosità ho deciso di scoprire se questo altro approccio fosse fattibile e di conseguenza è stato in grado di trovare una risposta migliore nel senso che non solo è più semplice del mio other, è anche un po 'più flessibile in la sensazione che sarebbe facile aggiungere argomenti alle funzioni chiamate come indicato in un commento qui sotto.

from string import Template 

def FooFunction(): return 15 
def BarFunction(): return 30 
def BazFunction(): return 6 

formula = "(($foo + $bar) - ($baz/2))" 

function_mapping = dict(foo='FooFunction()', # note these calls could have args 
         bar='BarFunction()', 
         baz='BazFunction()') 

converted_formula = Template(formula).substitute(function_mapping) 
print('converted_formula = "{}"'.format(converted_formula)) 

# define contexts in which to evalute the expression 
global_context = dict(FooFunction=FooFunction, 
         BarFunction=BarFunction, 
         BazFunction=BazFunction) 
local_context = dict(__builtins__=None) 
function = lambda: eval(converted_formula, global_context, local_context) 

answer = function() # call it 
print('answer = {}'.format(answer)) 

Come nota finale, notare che string.Template supporta diversi tipi di Uso avanzato che permetterebbe di mettere a punto la sintassi delle espressioni ancora di più - perché internamente usa il modulo re (in un modo più sofisticato di quanto ho fatto nella mia risposta originale).

Per i casi in cui tutte le funzioni mappate restituiscono valori che possono essere rappresentati come letterali Python - come numeri - e non vengono chiamati solo per gli effetti collaterali che producono, è possibile apportare la seguente modifica che memorizza in modo efficace (aka memoize) i risultati:

function_cache = dict(foo=FooFunction(), # calls and caches function results 
         bar=BarFunction(), 
         baz=BazFunction()) 

def evaluate(formula): 
    print('formula = {!r}'.format(formula)) 
    converted_formula = Template(formula).substitute(function_cache) 
    print('converted_formula = "{}"'.format(converted_formula)) 
    return eval(converted_formula, global_context, local_context) 

print('evaluate(formula) = {}'.format(evaluate(formula))) 

uscita:

formula = '(($foo + $bar) - ($baz/2))' 
converted_formula = "((15 + 30) - (6/2))" 
evaluate(formula) = 42 
Problemi correlati