2015-10-16 3 views
7

Я пытаюсь проанализировать некоторый грязный код, который довольно часто использует глобальные переменные внутри функций (я пытаюсь реорганизовать код, чтобы функции использовали только локальные переменные). Есть ли способ обнаружить глобальные переменные внутри функции?Обнаруживать все глобальные переменные в функции python?

Например:

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

Здесь глобальная переменная не y, так как это не дано в качестве аргумента, и ни оно создается внутри функции.

Я попытался обнаружить глобальные переменные внутри функции, используя синтаксический анализ строк, но он немного запутался; Мне было интересно, есть ли лучший способ сделать это?

Edit: Если кому-то интересно, это код, я использую для определения глобальных переменных (на основе ответа 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 по умолчанию не return output, поэтому, если вы хотите манипулировать выводом 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

Блестящий! Это был короткий сладкий пифонический ответ, который я искал. 'dis' выглядит как действительно классная библиотека, мне придется в этом разобраться. @idjaw 'print (globals())' будет печатать все глобалы в скрипте, а не только те, которые находятся в интересующей функции. – applecider

2

Как уже упоминалось в LOAD_GLOBAL documentation:

LOAD_GLOBAL (namei)

Грузы глобального имени co_names[namei] на стек.

Это означает, что вы можете проверить код объект для функции, чтобы найти глобал:

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

Обратите внимание, что это не является достаточным для вложенных функций (и не является dis.dis метода @ Kindall отвечает). В этом случае вам также нужно будет посмотреть на константы:

# 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

Смежные вопросы