2015-10-16 60 views
7

我想分析一些凌亂的代碼,這恰好在函數中使用全局變量相當沉重(我試圖重構代碼,以便函數只使用局部變量)。有什麼方法可以檢測函數內的全局變量嗎?檢測python函數中的所有全局變量?

例如:

def f(x): 
    x = x + 1 
    z = x + y 
    return z 

這裏全局變量是y,因爲它不作爲參數給出的,並且也不是該函數內創建的。

我試圖使用字符串解析來檢測函數內的全局變量,但它變得有點混亂;我想知道是否有更好的方法來做到這一點?

編輯:如果有人有興趣,這是我使用來檢測全局變量的代碼(基於kindall的答案和保羅的回答這個問題:Capture stdout from a script in Python):

from dis import dis 

def capture(f): 
    """ 
    Decorator to capture standard output 
    """ 
    def captured(*args, **kwargs): 
     import sys 
     from cStringIO import StringIO 

     # setup the environment 
     backup = sys.stdout 

     try: 
      sys.stdout = StringIO()  # capture output 
      f(*args, **kwargs) 
      out = sys.stdout.getvalue() # release output 
     finally: 
      sys.stdout.close() # close the stream 
      sys.stdout = backup # restore original stdout 

     return out # captured output wrapped in a string 

    return captured 

def return_globals(f): 
    """ 
    Prints all of the global variables in function f 
    """ 
    x = dis_(f) 
    for i in x.splitlines(): 
     if "LOAD_GLOBAL" in i: 
      print i 

dis_ = capture(dis) 

dis_(f) 

dis默認情況下不返回輸出,所以如果你想操作dis作爲一個字符串的輸出,你必須使用Paolo寫的捕獲裝飾器,併發布在這裏:Capture stdout from a script in Python

+0

碰巧我還寫了一種捕獲stdout的方法。 :-) http://stackoverflow.com/a/16571630/416467 – kindall

回答

7

檢查字節碼。

from dis import dis 
dis(f) 

結果:

2   0 LOAD_FAST    0 (x) 
       3 LOAD_CONST    1 (1) 
       6 BINARY_ADD 
       7 STORE_FAST    0 (x) 

    3   10 LOAD_FAST    0 (x) 
      13 LOAD_GLOBAL    0 (y) 
      16 BINARY_ADD 
      17 STORE_FAST    1 (z) 

    4   20 LOAD_FAST    1 (z) 
      23 RETURN_VALUE 

全局變量將有LOAD_GLOBAL操作碼,而不是LOAD_FAST。 (如果函數改變任何全局變量,會有STORE_GLOBAL操作碼爲好。)

隨着一點點的工作,你甚至可以寫掃描功能的字節碼,並返回它使用全局變量列表的功能。事實上:

from dis import HAVE_ARGUMENT, opmap 

def getglobals(func): 
    GLOBAL_OPS = opmap["LOAD_GLOBAL"], opmap["STORE_GLOBAL"] 
    EXTENDED_ARG = opmap["EXTENDED_ARG"] 

    func = getattr(func, "im_func", func) 
    code = func.func_code 
    names = code.co_names 

    op = (ord(c) for c in code.co_code) 
    globs = set() 
    extarg = 0 

    for c in op: 
     if c in GLOBAL_OPS: 
      globs.add(names[next(op) + next(op) * 256 + extarg]) 
     elif c == EXTENDED_ARG: 
      extarg = (next(op) + next(op) * 256) * 65536 
      continue 
     elif c >= HAVE_ARGUMENT: 
      next(op) 
      next(op) 

     extarg = 0 

    return sorted(globs) 

print getglobals(f)    # ['y'] 
+0

你對使用print(globals())有什麼想法? – idjaw

+1

這很大程度上取決於狀態,也就是說,哪些全局變量已被您所做的特定函數調用序列(假定某些函數設置爲全局變量)定義。'dis'更安全,因爲Python解析器在生成字節碼時已經決定了哪些變量是局部變量,所以它知道哪些變量必須是全局變量,即使它們還沒有被定義。 – kindall

+1

太棒了!那是我尋找的簡短的甜蜜pythonic答案。 'dis'看起來像一個非常酷的圖書館,我將不得不在後面深入研究。 @idjaw'print(globals())'會打印腳本中的所有全局變量,而不僅僅是那些感興趣的函數。 – applecider

2

正如LOAD_GLOBAL documentation提到:

LOAD_GLOBAL(對蝦)

加載全局命名co_names[namei]到堆棧中。

這意味着你可以檢查代碼對象爲你的功能查找全局:

>>> f.__code__.co_names 
('y',) 

注意,這是不夠的嵌套函數(也不是@ kindall的答案dis.dis方法)。在這種情況下,您也需要查看常量:

# Define a function containing a nested function 
>>> def foo(): 
... def bar(): 
...  return some_global 

# It doesn't contain LOAD_GLOBAL, so .co_names is empty. 
>>> dis.dis(foo) 
    2   0 LOAD_CONST    1 (<code object bar at 0x2b70440c84b0, file "<ipython-input-106-77ead3dc3fb7>", line 2>) 
       3 MAKE_FUNCTION   0 
       6 STORE_FAST    0 (bar) 
       9 LOAD_CONST    0 (None) 
      12 RETURN_VALUE 

# Instead, we need to walk the constants to find nested functions: 
# (if bar contain a nested function too, we'd need to recurse) 
>>> from types import CodeType 
>>> for constant in foo.__code__.co_consts: 
...  if isinstance(constant, CodeType): 
...   print constant.co_names 
('some_global',) 
+0

這是關於嵌套函數的一個好點。 – kindall