2017-07-10 10 views
15

Pracuję nad prezentacją dla kolegów, aby wyjaśnić podstawowe zachowanie i rozumowanie stojące za GIL, i odkryłem coś, czego nie mogłem wyjaśnić, podczas składania krótkiego wyjaśnienia liczenia odwołań. Wydaje się, że nowo zadeklarowane zmienne mają cztery odniesienia, zamiast tych, których oczekiwałbym. Na przykład, następujące Kod:Dlaczego nowo utworzona zmienna w Pythonie ma licznik ref czterech?

the_var = 'Hello World!' 
print('Var created: {} references'.format(sys.getrefcount(the_var))) 

Wyniki w tym wyjściowych:

Var created: 4 references 

I potwierdzone, że wynik był taki sam, jeśli stosuje się liczbę całkowitą> 100 (< 100 są wstępnie utworzonych i mieć większy ref-count) lub float i jeśli zadeklarowałem zmienną w zakresie funkcji lub w pętli. Wynik był taki sam. Zachowanie wydaje się być takie samo w wersjach 2.7.11 i 3.5.1.

Podjęto próbę debugowania sys.getrefcount, aby zobaczyć, czy tworzył on dodatkowe odniesienia, ale nie był w stanie wejść do funkcji (zakładam, że jest to bezpośrednie przejście do warstwy C).

Wiem, że dostanę co najmniej jedno pytanie na ten temat, kiedy przedstawię, i tak naprawdę jestem dość zaskoczony przez wynik. Czy ktoś może mi wyjaśnić to zachowanie?

+0

Czy piszesz to od razu w powłoce Pythona lub Pythona? lub uruchamiając go jako samodzielny skrypt? Dostaję 2 referencje w zwykłej powłoce Pythona. – Meitham

+0

Po uruchomieniu kodu w wersji 2.7.13 otrzymuję "Var created: 2 references'. – Alex

+0

Spodziewałbym się 2 referencji w trybie interaktywnym i 3 jako skryptu (jeden dodatkowy dla odniesienia ze stałych obiektu kodu), ale nie 4. Możliwe, że zrobiłeś coś innego, aby utworzyć inne odniesienie. – user2357112

Odpowiedz

13

Istnieje kilka scenariuszy, które przyniosą inną liczbę referencyjną. Najprostszym jest konsola REPL:

>>> import sys 
>>> the_var = 'Hello World!' 
>>> print(sys.getrefcount(the_var)) 
2 

Zrozumienie ten wynik jest całkiem prosta - jest jedna wzmianka w lokalnej stosu i kolejna tymczasowa/local do funkcji sys.getrefcount() (nawet documentation ostrzega o tym - The count returned is generally one higher than you might expect). Ale kiedy go uruchomić jako samodzielny skryptu:

import sys 

the_var = 'Hello World!' 
print(sys.getrefcount(the_var)) 
# 4 

jak zauważyłeś, masz 4. Co daje? Dobrze, pozwala zbadać ... Jest to bardzo pomocne interfejs do śmieciarza - moduł gc - więc jeśli uruchomić go w konsoli REPL:

>>> import gc 
>>> the_var = 'Hello World!' 
>>> gc.get_referrers(the_var) 
[{'__builtins__': <module '__builtin__' (built-in)>, '__package__': None, 'the_var': 'Hello 
World!', 'gc': <module 'gc' (built-in)>, '__name__': '__main__', '__doc__': None}] 

Brak cuda tam - to w zasadzie tylko prąd namespace (locals()) ponieważ zmienna nie istnieje nigdzie indziej. Ale co się dzieje, gdy prowadzimy że jako samodzielny skryptu:

import gc 
import pprint 

the_var = 'Hello World!' 
pprint.pprint(gc.get_referrers(the_var)) 

ten drukuje (YMMV, na podstawie swojej wersji Pythona):

[['gc', 
    'pprint', 
    'the_var', 
    'Hello World!', 
    'pprint', 
    'pprint', 
    'gc', 
    'get_referrers', 
    'the_var'], 
(-1, None, 'Hello World!'), 
{'__builtins__': <module '__builtin__' (built-in)>, 
    '__doc__': None, 
    '__file__': 'test.py', 
    '__name__': '__main__', 
    '__package__': None, 
    'gc': <module 'gc' (built-in)>, 
    'pprint': <module 'pprint' from 'D:\Dev\Python\Py27-64\lib\pprint.pyc'>, 
    'the_var': 'Hello World!'}] 

Rzeczywiście, mamy jeszcze dwie referencje z listy tak jak nam powiedział sys.getrefcount(), ale co to do cholery jest? Cóż, kiedy interpreter Pythona analizuje twój skrypt, najpierw musi on być compile podłączony do kodu bajtowego - i podczas gdy on to robi, przechowuje wszystkie łańcuchy na liście, która, ponieważ wspomina również twoją zmienną, jest zadeklarowana jako odniesienie do niej.

Drugi bardziej tajemniczy wpis ((-1, None, 'Hello World!')) pochodzi z peep-hole optimizer i tam właśnie właśnie optymalizuje dostęp (w tym przypadku odwołanie do łańcucha).

Oba te mają charakter tymczasowy i opcjonalnie - konsola REPL robi separację kontekstowe więc nie widzisz tych odniesień, jeśli były do ​​„outsourcingu” swoją kompilację ze swojej obecnej sytuacji:

import gc 
import pprint 

exec(compile("the_var = 'Hello World!'", "<string>", "exec")) 
pprint.pprint(gc.get_referrers(the_var)) 

ty „d dostać:

[{'__builtins__': <module '__builtin__' (built-in)>, 
    '__doc__': None, 
    '__file__': 'test.py', 
    '__name__': '__main__', 
    '__package__': None, 
    'gc': <module 'gc' (built-in)>, 
    'pprint': <module 'pprint' from 'D:\Dev\Python\Py27-64\lib\pprint.pyc'>, 
    'the_var': 'Hello World!'}] 

a jeśli były, aby wrócić do oryginalnego próbę uzyskania licznika odwołań poprzez sys.getreferencecount():

import sys 

exec(compile("the_var = 'Hello World!'", "<string>", "exec")) 
print(sys.getrefcount(the_var)) 
# 2 

podobnie jak w konsoli REPL i zgodnie z oczekiwaniami. Dodatkowe odniesienie ze względu na optymalizację peep-hole, ponieważ dzieje się to w miejscu, może być natychmiast odrzucone przez wymuszenie zbierania śmieci (gc.collect()) przed zliczeniem referencji.

Jednak lista napisów utworzona podczas kompilacji nie może zostać zwolniona, dopóki cały plik nie zostanie przeanalizowany i skompilowany, dlatego jeśli miałbyś zaimportować swój skrypt do innego skryptu, a następnie policzyć odniesienia do niego the_var dostaniesz 3 zamiast 4 właśnie wtedy, gdy myślałeś, że już nie może ci to zmylić;)

+0

Nie mogę odtworzyć danych wyjściowych, które widzisz - wpisów '4' lub dodatkowych' gc.get_referrers() '. Do czego używasz do uruchomienia tego kodu? – user2357112

+0

@ user2357112 - chyba że coś się zmieniło w najnowszych wersjach CPython, powinno to być domyślne zachowanie dla standardowego wykonywania 'python test_script.py'. Oczywiście, jeśli skompilujesz swój skrypt do PYC i uruchomisz go, nie otrzymasz "4" - całe dodatkowe referencje wynikają z procedur kompilacji/optymalizacji. Kiedy dostanę czas, przyjrzę się najnowszemu źródłu CPython, ale nie sądzę, żeby to się znacząco zmieniło. – zwer

+0

Próbuję go na 3.6 (ponieważ zapomniałem, że Ideone był na 3.5) odtwarza '4' i listę, ale nie' '(-1, Żadne, 'Hello World!')' Krotki. – user2357112

Powiązane problemy