2012-09-17 43 views
9

在我的代碼中,我使用eval來評估用戶給出的字符串表達式。有沒有方法可以編譯或以其他方式加快此聲明?Python:加速反覆執行的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 

對於一個快速的比較方案一需要我的機器上2.019秒,而方案二隻需要0.218秒。當然,Python沒有硬編碼表達式的方法。

+4

看看這個帖子http://stackoverflow.com/questions/1832940以及一些很好的理由遠離它的eval的一些替代品。 –

+2

如果用戶鍵入'import os; os.system(「rm -rf /」)'?你需要編寫一個解析器來解釋輸入字符串,並且只識別你期望的:'sin','cos','log'等等。如果輸入的內容不起作用,則拋出一個錯誤。如果你不這樣做可能會很糟糕。 – jozzas

+0

如果用戶想要「rm -rf /」或「:(){:|:& };:」他可以在shell中而不是在Python中完成。 – devtk

回答

16

您也可以欺騙蟒:

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

,然後用它像這樣:

exp_as_func() 

速度測試:

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 

作爲一個側面說明,如果v不是一個全球性的,你可以cr eate拉姆達這樣的:

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

,並稱之爲:

exp_as_func(my_v) 
+2

這是一個顯着的速度提高超過F.J.的迴應,這已經是一個很大的速度提高。 – devtk

+0

我猜這個技巧相當於在eval之前使用'compile',因爲當你運行它時,你會得到'最慢的運行比最快的運行長17.90倍。這可能意味着中間結果正被緩存。 – Mermoz

11

您可以通過使用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 

只要確保你的編譯只有一次(環外)。正如評論中所述,在用戶提交的字符串上使用eval時,請確保您對所接受的內容非常小心。

+0

這是一個非常顯着的收益...... –

4

我認爲你是優化了錯誤的結束。如果你想爲一個批號的執行相同的操作,你應該考慮使用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)) 

要給大家介紹我使用通用的Python和拉姆達招加一個變體可能得到了一個主意:

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) 

這裏是我的老機器上的結果:

$ 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 

的加速並不如我預期的那樣高,但是仍然顯著。

+0

我在這裏無法訪問'numpy'。但我同意,它可能會加快速度。如果沒有它,我通常會反對依賴第三方庫。 – devtk