2015-10-16 25 views
7

Próbuję przeanalizować trochę niechlujnego kodu, który zdarza się używać zmiennych globalnych całkiem mocno w funkcjach (próbuję zreorganizować kod, aby funkcje używały zmiennych lokalnych). Czy istnieje sposób na wykrycie zmiennych globalnych w funkcji?Wykrywanie wszystkich zmiennych globalnych w funkcji Pythona?

Na przykład:

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

Tutaj zmienna globalna jest y ponieważ nie jest podany jako argument, i nie jest on stworzony w ramach funkcji.

Próbowałem wykryć globalne zmienne w funkcji za pomocą analizy parsowania, ale robiło się trochę nieporządnie; Zastanawiałem się, czy istnieje lepszy sposób na zrobienie tego?

Edycja: Jeśli ktoś jest zainteresowany to jest kod używam wykryć zmienne globalne (na podstawie odpowiedzi kindall i odpowiedzi Paola na to pytanie: 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 domyślnie nie return output, więc jeśli chcesz manipulować wyjściem dis jako ciągiem, musisz użyć dekoratora przechwytywania napisanego przez Paolo i opublikowanego tutaj: Capture stdout from a script in Python

+0

Tak jak to się stało, napisałem również sposób na przechwytywanie stdout. :-) http://stackoverflow.com/a/16571630/416467 – kindall

Odpowiedz

7

Sprawdź kod bajtowy.

from dis import dis 
dis(f) 

Wynik:

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 

Zmienne globalne będą miały LOAD_GLOBAL opcodu zamiast LOAD_FAST. (Jeśli funkcja zmieni jakiekolwiek zmienne globalne, będą również dostępne kody opcyjne STORE_GLOBAL.)

Przy odrobinie pracy można nawet napisać funkcję, która skanuje bajt kodu funkcji i zwraca listę zmiennych globalnych, których używa . W rzeczywistości:

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

Jakie są twoje przemyślenia na temat używania print (globals())? – idjaw

+1

To zależy w dużym stopniu od stanu, tj. Które zmienne globalne zostały zdefiniowane przez określoną sekwencję wywołań funkcji, które wykonałeś (zakładając niektóre z funkcji ustawionych globalnie).'dis' jest bezpieczniejsze, ponieważ parser w języku Python już zdecydował, które zmienne są lokalne, gdy generuje kod bajtowy, więc wie, które muszą być globalne, nawet jeśli nie zostały jeszcze zdefiniowane. – kindall

+1

Genialny! To była krótka słodka pytonowa odpowiedź, której szukałem. 'dis' wygląda na naprawdę fajną bibliotekę, będę musiał zagłębić się w to później. @idjaw 'print (globals())' wypisze wszystkie globale w skrypcie, a nie tylko te w ramach interesującej nas funkcji. – applecider

2

Jak wspomniano w LOAD_GLOBAL documentation:

LOAD_GLOBAL (namei)

Ładuje globalny nazwany co_names[namei] na stos.

Oznacza to, że można skontrolować obiekt kodu dla funkcji znaleźć globalnych:

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

Zauważ, że nie jest to wystarczające dla funkcji zagnieżdżonych (nie jest to metoda na @ kindall odpowiedź dis.dis). W takim przypadku będziesz musiał również przyjrzeć się stałym:

# 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

To jest dobry punkt o zagnieżdżonych funkcjach. – kindall