2012-09-17 8 views
9

W moim kodzie używam eval do oceny wyrażenia łańcuchowego podanego przez użytkownika. Czy istnieje sposób, aby skompilować lub w inny sposób przyspieszyć to oświadczenie?Python: Sposób na przyspieszenie wielokrotnie wykonywanej instrukcji eval?

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 

Na szybki opcji porównanie weźmie 2.019 sekund na moim komputerze, podczas gdy opcja DWA trwa zaledwie 0,218 sekundy. Z pewnością Python ma sposób robienia tego bez twardego kodowania ekspresji.

+4

Sprawdź niektóre alternatywy dla eval w tym poście http://stackoverflow.com/questions/1832940, a także kilka dobrych powodów, aby trzymać się z daleka od tego. –

+2

co, jeśli użytkownik wpisze "import os; os.system (" rm -rf/")'? Musisz napisać parser, aby zinterpretować wejściowy ciąg znaków i rozpoznać tylko to, czego się spodziewasz: 'sin',' cos', 'log', itp. Zgłaszanie błędów, jeśli to, co wpisano, nie działa. Może być źle, jeśli tego nie zrobisz. – jozzas

+0

Jeśli użytkownik chce "rm -rf /" lub ":() {: |: & };:" może zrobić to w powłoce zamiast w Pythonie. – devtk

Odpowiedz

16

Można również oszukać Pythona:

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

a następnie używać go tak:

exp_as_func() 

Test Speed:

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 

Na marginesie, jeśli v nie jest globalna, możesz cr eate lambda tak:

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

i nazywają go:

exp_as_func(my_v) 
+2

Jest to zauważalna poprawa szybkości w porównaniu z reakcją F.J., która była już dużą poprawą szybkości. – devtk

+0

Domyślam się, że ta sztuczka jest równoważna użyciu 'compile' przed eval, ponieważ kiedy ją uruchomisz, uzyskasz' Najwolniejszy bieg wziął 17.90 razy dłużej niż najszybszy. Może to oznaczać buforowanie wyniku pośredniego ". – Mermoz

11

można uniknąć napowietrznej kompilując wyrażenie korzystając compiler.compile():

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 

Wystarczy upewnić się, wykonaj kompilacji tylko raz (na zewnątrz pętli). Jak wspomniano w komentarzach, podczas korzystania z eval na ciągach przesłanych przez użytkownika upewnij się, że jesteś bardzo ostrożny, co akceptujesz.

+0

to całkiem znaczący zysk ... –

4

Chyba jesteś optymalizacji zły koniec. Jeśli chcesz, aby wykonać tę samą operację dla wielu liczb należy rozważyć użycie numpy:

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)) 

Aby dać wyobrażenie o ewentualnym zysku Dodałem wariant stosując ogólne Python i sztuczki lambda:

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) 

Oto wyniki na moim komputerze starzenie:

$ 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 

prędkość-up nie jest tak wysokie, jak się spodziewałem, ale nadal znaczące.

+0

Nie mam tutaj dostępu do 'numpy'. Ale zgadzam się, może to przyspieszyć. Generalnie jestem przeciwny poleganiu na bibliotece stron trzecich, jeśli mogę bez niej przejść. – devtk