2012-09-17 5 views
9

In meinem Code verwende ich eval, um einen vom Benutzer angegebenen Zeichenfolgenausdruck auszuwerten. Gibt es eine Möglichkeit, diese Aussage zu kompilieren oder anderweitig zu beschleunigen?Python: Möglichkeit, eine wiederholt ausgeführte Eval-Anweisung zu beschleunigen?

import math 
import random 

result_count = 100000 
expression = "math.sin(v['x']) * v['y']" 

variable = dict() 
variable['x'] = [random.random() for _ in xrange(result_count)] 
variable['y'] = [random.random() for _ in xrange(result_count)] 

# optimize anything below this line 

result = [0] * result_count 

print 'Evaluating %d instances of the given expression:' % result_count 
print expression 

v = dict() 
for index in xrange(result_count): 
    for name in variable.keys(): 
     v[name] = variable[name][index] 
    result[index] = eval(expression) # <-- option ONE 
    #result[index] = math.sin(v['x']) * v['y'] # <-- option TWO 

Für einen schnellen Vergleich Option ONE nimmt 2,019 Sekunden auf meiner Maschine, während Option ZWEI nur 0,218 Sekunden dauert. Sicherlich hat Python eine Möglichkeit, dies zu tun, ohne den Ausdruck hart zu codieren.

+4

Überprüfen Sie einige Alternativen zu Eval in diesem Beitrag http://StackOverflow.com/Questions/1832940 sowie einige gute Gründe, sich davon fern zu bleiben. –

+2

Was passiert, wenn der Benutzer 'import os; os.system (" rm -rf/")'? Sie müssen einen Parser schreiben, um die Eingabe-Zeichenkette zu interpretieren, und nur erkennen, was Sie erwarten: 'sin',' cos', 'log', etc. Wirf einen Fehler, wenn das, was sie eingeben, nicht funktioniert. Es könnte schlimm sein, wenn du das nicht tust. – jozzas

+0

Wenn der Benutzer möchte "rm -rf /" oder ":() {: |: & };:" Er kann es in einer Shell statt in Python tun. – devtk

Antwort

16

können Sie auch Python Trick:

expression = "math.sin(v['x']) * v['y']" 
exp_as_func = eval('lambda: ' + expression) 

Und es dann wie so verwenden:

exp_as_func() 

Geschwindigkeitstest:

In [17]: %timeit eval(expression) 
10000 loops, best of 3: 25.8 us per loop 

In [18]: %timeit exp_as_func() 
1000000 loops, best of 3: 541 ns per loop 

Als Randbemerkung, wenn v ist kein globaler, können Sie cr eate die Lambda wie folgt aus:

exp_as_func = eval('lambda v: ' + expression) 

und nennen es:

exp_as_func(my_v) 
+2

Dies ist eine spürbare Geschwindigkeitsverbesserung gegenüber F.J.'s Antwort, die bereits eine große Geschwindigkeitsverbesserung war. – devtk

+0

Ich denke, dieser Trick ist gleichbedeutend mit "compile" vor eval, denn wenn Sie es ausführen, erhalten Sie 'Der langsamste Lauf dauerte 17,90 mal länger als der schnellste. Dies könnte bedeuten, dass ein Zwischenergebnis zwischengespeichert wird. – Mermoz

11

Sie können den Aufwand vermeiden, indem Sie den Ausdruck im Voraus Kompilieren compiler.compile() mit:

In [1]: import math, compiler 

In [2]: v = {'x': 2, 'y': 4} 

In [3]: expression = "math.sin(v['x']) * v['y']" 

In [4]: %timeit eval(expression) 
10000 loops, best of 3: 19.5 us per loop 

In [5]: compiled = compiler.compile(expression, '<string>', 'eval') 

In [6]: %timeit eval(compiled) 
1000000 loops, best of 3: 823 ns per loop 

Sie sicher, dass Sie nur einmal das Kompilieren Sie (außerhalb der Schleife). Wie in den Kommentaren erwähnt, wenn Sie eval auf von Benutzern übergebenen Zeichenfolgen verwenden, stellen Sie sicher, dass Sie sehr vorsichtig sind, was Sie akzeptieren.

+0

das ist ein ziemlich signifikanter Gewinn ... –

4

Ich glaube, Sie das falsche Ende optimieren. Wenn Sie den gleichen Vorgang für viele Zahlen ausführen möchten, sollten Sie prüfen, numpy mit:

import numpy 
import time 
import math 
import random 

result_count = 100000 
expression = "sin(x) * y" 

namespace = dict(
    x=numpy.array(
     [random.random() for _ in xrange(result_count)]), 
    y=numpy.array(
     [random.random() for _ in xrange(result_count)]), 
    sin=numpy.sin, 
) 
print ('Evaluating %d instances ' 
     'of the given expression:') % result_count 
print expression 

start = time.time() 
result = eval(expression, namespace) 
numpy_time = time.time() - start 
print "With numpy:", numpy_time 


assert len(result) == result_count 
assert all(math.sin(a) * b == c for a, b, c in 
      zip(namespace["x"], namespace["y"], result)) 

Sie über die mögliche Verstärkung eine Idee geben ich eine Variante mit generic Python und den Lambda-Trick hinzugefügt haben:

from math import sin 
from itertools import izip 

start = time.time() 
f = eval("lambda: " + expression) 
result = [f() for x, y in izip(namespace["x"], namespace["y"])] 
generic_time = time.time() - start 
print "Generic python:", generic_time 
print "Ratio:", (generic_time/numpy_time) 

Hier sind die Ergebnisse auf meiner alternden Maschine:

$ python speedup_eval.py 
Evaluating 100000 instances of the given expression: 
sin(x) * y 
With numpy: 0.006098985672 
Generic python: 0.270224094391 
Ratio: 44.3063992807 

Die Speed-up ist nicht so hoch wie ich erwartet hatte, aber immer noch signifikant.

+0

Ich habe hier keinen Zugriff auf "numpy". Aber ich stimme zu, es könnte die Dinge beschleunigen. Ich bin generell dagegen, mich auf eine Drittanbieter-Bibliothek zu verlassen, wenn ich ohne sie auskommen kann. – devtk

Verwandte Themen