2016-05-03 20 views
6

chciałbym wykonać następujące czynności:Warn dla każdego (zagnieżdżonych) funkcji o zmiennych wolnych (rekurencyjnie)

for every nested function f anywhere in this_py_file: 
    if has_free_variables(f): 
     print warning 

Dlaczego? Przede wszystkim jako ubezpieczenie od późnego wiążącego zamknięcia, otrzymałem numer described elsewhere. Mianowicie:

>>> def outer(): 
...  rr = [] 
...  for i in range(3): 
...   def inner(): 
...    print i 
...   rr.append(inner) 
...  return rr 
... 
>>> for f in outer(): f() 
... 
2 
2 
2 
>>> 

I ilekroć się ostrzeżony o zmiennej wolnej, chciałbym też dodać wyraźny wyjątek (w rzadkich przypadkach, że chciałbym to zachowanie) lub go naprawić tak:

 
...   def inner(i=i): 

Następnie zachowanie staje się bardziej podobne do klas zagnieżdżonych w Javie (gdzie dowolną zmienną do użycia w klasie wewnętrznej musi być final).

(O ile mi wiadomo, oprócz rozwiązania problemu późno wiążącego, przyczyni się to również do lepszego wykorzystania pamięci, ponieważ jeśli funkcja "zamyka" niektóre zmienne w zakresie zewnętrznym, wówczas zasięg zewnętrzny nie może być śmieciem zbierane tak długo, jak funkcja jest w pobliżu. Tak?)

Nie mogę znaleźć żadnego sposobu na uzyskanie funkcji zagnieżdżonych w innych funkcjach. Obecnie najlepszym sposobem, jaki mogę sobie wyobrazić, jest instrumentowanie parsera, co wydaje się dużym nakładem pracy.

+0

Co dokładnie próbujesz zrobić? – kpie

+0

To jest [problem XY] (http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) na masową skalę. Jeśli martwisz się o późno wiążące zamknięcia w kawałku kodu, musisz napisać kod, aby go nie cierpieć_, nie rób masowej rzeczy z wyjątkiem stylu po fakcie. Czy nie masz kontroli nad kodem, który mógłby potencjalnie to zrobić? –

+0

Również ludzie pytali o [późno wiążące zamknięcia wcześniej] (http://stackoverflow.com/questions/6035848/python-closure-not-working-as-expected) i otrzymywali odpowiedzi, które pomagają złagodzić to samo. Podążanie za tobą mogłoby ci lepiej służyć niż obecne podejście. –

Odpowiedz

2

Rozważmy następującą funkcję:

def outer_func(): 
    outer_var = 1 

    def inner_func(): 
     inner_var = outer_var 
     return inner_var 

    outer_var += 1 
    return inner_func 

Przedmiotem __code__ może być użyty do odzyskania obiektu kodu funkcji wewnętrznej:

outer_code = outer_func.__code__ 
inner_code = outer_code.co_consts[2] 

z tego obiektu kodu wolne zmienne mogą być odzyskane:

inner_code.co_freevars # ('outer_var',) 

Można sprawdzić, czy dany obiekt kod powinien być kontrolowany z:

hasattr(inner_code, 'co_freevars') # True 

Po otrzymaniu wszystkich funkcji z pliku, może to wyglądać mniej więcej tak:

for func in function_list: 
    for code in outer_func.__code__.co_consts[1:-1]: 
     if hasattr(code, 'co_freevars'): 
      assert len(code.co_freevars) == 0 

Ktoś, kto wie więcej o wewnętrznych działaniach, może prawdopodobnie dostarczyć lepszego wyjaśnienia lub bardziej zwięzłego rozwiązania.

0

Aby "uzyskać blokadę" funkcji zagnieżdżonych (nawet jeśli nadpisujesz je), musisz użyć eval, aby utworzyć nazwy definicji zmiennych dla każdej deklaracji.

def outer(): 
    rr = [] 
    for i in range(3): 
     eval("def inner"+str(i)+"""(): 
      print """+str(i)) 
     rr.append(eval("inner"+str(i))) 
    return rr 

for f in outer(): f() 

drukuje

1 
2 
3 
+0

Zmusza to zamknięcie do oceny w czasie przydziału, co jest dobre, ale nie zawsze jest właściwym rozwiązaniem. Wyobraź sobie, że chcesz zakodować funkcję stanową, która ma na celu, powiedzmy, wywoływanie baz danych później w kodzie - to zmusiłoby wywołanie bazy danych natychmiast, a nie później zgodnie z planem. To jest hack, a nie kompletne rozwiązanie. Dobra próba: D –

+0

Interesujące. mój kod nie działa. i tutaj byłem taki pewny ... Zwykły dylemat. – kpie

+2

Nie wspominałem o tym, ale powinno być jasne, że prawdziwym rozwiązaniem musi być funkcja "ustaw i zapomnij". Stanie się częścią uprzęży programistycznej i nie ma absolutnie żadnego wpływu na styl kodowania lub zmianę istniejącego kodu. Nawet dodanie dekoratora do każdej funkcji zagnieżdżonej byłoby przesadą. –

-1

trzeba import copy i używać rr.append(copy.copy(inner))

https://pymotw.com/2/copy/

+0

To nie odpowiada na pytanie. Nie prosi o uniknięcie późno wiążących zamknięć, prosi o * wykrycie * ich. –

+0

Niestety to nie działa. Wydaje mi się, że różne funkcje "wewnętrzne()" są tworzone w pewnym sensie (w pewnym sensie), ale problem polega na tym, że wszystkie zachowują swobodne odniesienie do zmiennej (używając terminologii w pewnym luźnym sensie) do zewnętrznego zakresu, a jedynie sprawdzają wartość 'i' w tym zakresie, gdy są wywoływane. W tym czasie jest to "2". Byłoby to dziwne semantyki z punktu widzenia modelu języka C++/Java; to bardziej przypomina to, co dzieje się w JavaScript i może w Ruby. –

0

Chciałem również zrobić to w Jython. Ale sposób przedstawiony w zaakceptowanej odpowiedzi tam nie działa, ponieważ co_consts nie jest dostępny w obiekcie kodu. (Wydaje się, że nie ma żadnego innego sposobu na zapytanie obiektu kodu, aby uzyskać obiekty kodu funkcji zagnieżdżonych.)

Ale oczywiście obiekty kodu są gdzieś, mamy źródło i pełny dostęp, więc jest to tylko kwestia znalezienia łatwej drogi w rozsądnym czasie. Oto jeden sposób, który działa. (Trzymaj się swoich mandatów.)

Załóżmy, że mamy kod takiego modułu mod:

def outer(): 
    def inner(): 
     print "Inner" 

Pierwszy uzyskać obiekt kodu funkcji zewnętrznej bezpośrednio:

code = mod.outer.__code__ 

W Jython, jest to instancja PyTableCode, a czytając źródło, stwierdzamy, że rzeczywiste funkcje są implementowane w klasie Java utworzonej z danego skryptu, do którego odwołuje się pole obiektu kodu funcs. (Wszystkie te klasy wykonane ze skryptów są podklasami PyFunctionTable, stąd jest to zadeklarowany typ funcs.) Nie jest to widoczne z poziomu Jython, w wyniku magicznej maszyny, która jest sposobem projektanta na powiedzenie, że uzyskujesz dostęp do tych rzeczy na własne ryzyko.

Musimy więc na chwilę zanurkować w Javie. Klasa jak to załatwia sprawę:

import java.lang.reflect.Field; 

public class Getter { 
    public static Object getFuncs(Object o) 
    throws NoSuchFieldException, IllegalAccessException { 
     Field f = o.getClass().getDeclaredField("funcs"); 
     f.setAccessible(true); 
     return f.get(o); 
    } 
} 

Powrót w Jython:

>>> import Getter 
>>> funcs = Getter.getFuncs(mod.outer.__code__) 
>>> funcs 
[email protected] 

Teraz to funcs obiekt posiada wszystkie funkcje zadeklarowane w dowolnym miejscu skryptu Jython (te zagnieżdżone arbitralnie, w klasach, itp.). Ponadto posiada pola zawierające odpowiednie obiekty kodu.

>>> fields = funcs.class.getDeclaredFields() 

W moim przypadku, przedmiot kod odpowiadający funkcji zagnieżdżonych dzieje się ostatnia:

>>> flast = fields[-1] 
>>> flast 
static final org.python.core.PyCode mod$py.inner$24 

Aby uzyskać obiekt kodu zainteresowania:

>>> flast.setAccessible(True) 
>>> inner_code = flast.get(None) #"None" because it's a static field. 
>>> dir(inner_code) 
co_argcount co_filename co_flags co_freevars co_name co_nlocals co_varnames 
co_cellvars co_firstlineno 

A reszta jest taka sama jak zaakceptowana odpowiedź, to znaczy sprawdź co_freevars, (która jest tam w Jython, w przeciwieństwie do co_consts).

Dobrą rzeczą w tym podejściu jest to, że wyliczasz dokładnie wszystkie obiekty kodu, które są zadeklarowane w dowolnym miejscu w pliku kodu źródłowego: funkcje, metody, generatory, niezależnie od tego, czy są zagnieżdżone pod cokolwiek czy pod sobą. Nie ma się gdzie ukryć.

Powiązane problemy