2009-08-14 19 views
2

假設我要執行的代碼,例如exec()字節碼與任意本地人?

value += 5 

我自己的命名空間裏(所以結果基本上是mydict['value'] += 5)。有一個功能exec(),但我要傳遞一個字符串有:

exec('value += 5', mydict) 

並通過報表字符串似乎有些奇怪(例如它沒有着色的那樣)。 可以這樣做:

def block(): 
     value += 5 

    ???(block, mydict) 

?最後一行的明顯候選人是exec(block.__code__, mydict),但沒有運氣:它提高了UnboundLocalErrorvalue。我相信它基本上執行block(),而不是裏面的代碼,所以分配並不容易 - 這是正確的嗎?

當然,另一種可能的解決方案是拆卸block.__code__ ...

僅供參考,我得到了,因爲this thread的問題。同時,這也是爲什麼一些(我未定)呼籲新的語法

using mydict: 
     value += 5 

注意如何不會引發錯誤,但不會改變mydict之一:

def block(value = 0): 
     value += 5 

    block(**mydict) 
+0

這有什麼錯簡單的類定義一個人的輝煌配方只是一個猴子修補適應? – 2009-08-15 02:40:04

+0

我不確定你對班級的意思。我會盡量發表一個答案,概述一個可能的想法,但您可以隨時糾正我。 – 2009-08-21 22:42:38

回答

6

可以傳遞字節碼而不是字符串到exec,你只需要爲此目的製作正確的字節碼:

>>> bytecode = compile('value += 5', '<string>', 'exec') 
>>> mydict = {'value': 23} 
>>> exec(bytecode, mydict) 
>>> mydict['value'] 
28 

具體來說,...:

>>> import dis 
>>> dis.dis(bytecode) 
    1   0 LOAD_NAME    0 (value) 
       3 LOAD_CONST    0 (5) 
       6 INPLACE_ADD   
       7 STORE_NAME    0 (value) 
      10 LOAD_CONST    1 (None) 
      13 RETURN_VALUE   

的加載和存儲指令必須是_NAME勸說,這compile讓他們如此,而...:

>>> def f(): value += 5 
... 
>>> dis.dis(f.func_code) 
    1   0 LOAD_FAST    0 (value) 
       3 LOAD_CONST    1 (5) 
       6 INPLACE_ADD   
       7 STORE_FAST    0 (value) 
      10 LOAD_CONST    0 (None) 
      13 RETURN_VALUE   

...代碼一個函數被優化爲使用_FAST版本,並且那些不適用於傳遞給exec的字典。如果您使用_FAST指令以某種方式啓動了字節碼,則可以將其修補爲使用_NAME類型,例如與bytecodehacks或一些類似的方法。

+0

在這種情況下,看起來像黑客入侵字節碼是一個好主意,特別是因爲我想要的函數'apply_code_to_dict'的語義很簡單。 – 2009-08-21 22:40:15

3

使用global關鍵字來強制要從塊內修改任何變量的動態作用域:

def block(): 
    global value 
    value += 5 

mydict = {"value": 42} 
exec(block.__code__, mydict) 
print(mydict["value"]) 
0

從美國洛特的評論上面,我認爲我的想法,使用新創建的答案類。

class _(__metaclass__ = change(mydict)): 
    value += 1 
    ... 

其中change是一個元類,其__prepare__讀字典,其__new__更新字典。

重用,代碼段下面的工作,但它是一種醜惡:

def increase_value(d): 
    class _(__metaclass__ = change(d)): 
     value += 1 
     ... 

increase_value(mydict) 
3

這是一個瘋狂的裝飾,以創建一個使用「定製機」這樣的塊。實際上,將函數內部的所有變量訪問權限轉換爲全局訪問權限並以自定義locals字典作爲環境來評估結果是一件很快捷的事情。

import dis 
import functools 
import types 
import string 

def withlocals(func): 
    """Decorator for executing a block with custom "local" variables. 

    The decorated function takes one argument: its scope dictionary. 

    >>> @withlocals 
    ... def block(): 
    ...  counter += 1 
    ...  luckynumber = 88 

    >>> d = {"counter": 1} 
    >>> block(d) 
    >>> d["counter"] 
    2 
    >>> d["luckynumber"] 
    88 
    """ 
    def opstr(*opnames): 
     return "".join([chr(dis.opmap[N]) for N in opnames]) 

    translation_table = string.maketrans(
      opstr("LOAD_FAST", "STORE_FAST"), 
      opstr("LOAD_GLOBAL", "STORE_GLOBAL")) 

    c = func.func_code 
    newcode = types.CodeType(c.co_argcount, 
          0, # co_nlocals 
          c.co_stacksize, 
          c.co_flags, 
          c.co_code.translate(translation_table), 
          c.co_consts, 
          c.co_varnames, # co_names, name of global vars 
          (), # co_varnames 
          c.co_filename, 
          c.co_name, 
          c.co_firstlineno, 
          c.co_lnotab) 

    @functools.wraps(func) 
    def wrapper(mylocals): 
     return eval(newcode, mylocals) 
    return wrapper 

if __name__ == '__main__': 
    import doctest 
    doctest.testmod() 

這是一個goto decorator

+0

而不是一個裝飾器,更理智的使用可能會使它只是一個函數調用'withlocals(block,d)' – u0b34a0f6ae 2009-12-04 17:39:15

+0

哇,這很好。 – 2009-12-06 16:56:28

+0

謝謝。我懷疑馬爾泰利比我更瞭解這件事,儘管我只是設法把「有效」的東西放在一起(慚愧地稱之爲工作)..也許'LOAD_NAME'比'LOAD_GLOBAL'好? – u0b34a0f6ae 2009-12-06 17:23:54