2017-07-25 47 views
6

Jak mogę zatrzymać Pythona przed usunięciem powiązania nazwy, gdy ta nazwa jest używana do wiązania wyjątku, który został przechwycony? Kiedy ta zmiana w zachowaniu pojawiła się w Pythonie?Powiązanie nazwy w klauzuli "except" usunięto po klauzuli

piszę kod do uruchomienia zarówno Python 2 i Python 3:

exc = None 
try: 
    1/0 
    text_template = "All fine!" 
except ZeroDivisionError as exc: 
    text_template = "Got exception: {exc.__class__.__name__}" 

print(text_template.format(exc=exc)) 

Uwaga, exc jest wyraźnie związany przed obsługę wyjątków, więc Python wie, że jest to nazwa w zakresie zewnętrznej.

W Pythonie 2.7, to działa dobrze, a nazwa exc przetrwa do stosowania w format wezwanie ::

Got exception: ZeroDivisionError 

Wielki, to jest dokładnie to, co chcę: Klauzula except wiąże nazwę i Mogę użyć tej nazwy w dalszej części funkcji, aby odnieść się do obiektu wyjątku .

W Pythonie 3.5, wywołanie format powiodło się, ponieważ najwyraźniej exc wiązanie usunięte ::

Traceback (most recent call last): 
    File "<stdin>", line 8, in <module> 
NameError: name 'exc' is not defined 

Dlaczego exc wiązaniami usunięty z zakresu zewnętrznej? W jaki sposób mamy oznaczać , aby niezawodnie zachować nazwę powiązania, aby móc z niego korzystać po z klauzulą ​​except ?

Kiedy ta zmiana pojawiła się w Pythonie, gdzie jest udokumentowana?

Czy mam rację zgłaszając to jako błąd w Pythonie 3?

+0

wygląda to zamierzone. Jeśli przypiszesz 'exc' do innej zmiennej w klauzuli' except', możesz ją tam pobrać. Możliwy duplikat https://stackoverflow.com/questions/29268892/python-3-exception-deletes-variable-in-enclosing-scope-for-unknown-reason Wiedziałem, że powinienem był odpowiedzieć na to pytanie ... lol –

Odpowiedz

6

Nie, to nie jest błąd. Zachowanie, którego doświadczasz, jest wyraźnie i jednoznacznie zdefiniowane w Python 3 documentation for the try/except statement. Przyczyną takiego zachowania są także:

Kiedy wyjątek został przydzielony przez as target, to jest usuwany na końcu klauzuli except. To jest tak,

except E as N: 
    foo 

został przeliczeniu na

except E as N: 
    try: 
     foo 
    finally: 
     del N 

Oznacza to wyjątek musi być przypisany do innej nazwy, aby móc się do niego po klauzuli except. Wyjątki są usuwane, ponieważ po powiązaniu z nimi traceback tworzą cykl odniesienia z ramką stosu, utrzymując wszystkich mieszkańców w tej ramce przy życiu, dopóki nie nastąpi następny proces zbierania śmieci.

Powodem deklarowania nazwy poza zakresem bloku try/except nie działał dlatego użyłeś exc w klauzuli as. Tak więc została usunięta nazwa Python.

Rozwiązaniem jest użycie innej nazwy w klauzuli as związać wyjątku, a następnie przypisać zmienną globalną z inną nazwą wyjątek:

>>> exc_global = None 
>>> try: 
    1/0 
    text_template = "All fine!" 
except ZeroDivisionError as exc: 
    exc_global = exc 
    text_template = "Got exception: {exc.__class__.__name__}" 


>>> print(text_template.format(exc=exc_global)) 
Got exception: ZeroDivisionError 

Jak zauważył Anthony Sottile w uwag demontaż kodu try/except również wyraźnie wspiera powyższe wypowiedzi dokumentacji:

>>> code = """ 
try: 
    1/0 
    text_template = "All fine!" 
except ZeroDivisionError as exc: 
    text_template = "Got exception: {exc.__class__.__name__}" 
""" 
>>> from dis import dis 
>>> dis(code) 
    2   0 SETUP_EXCEPT   16 (to 18) 

    3   2 LOAD_CONST    0 (1) 
       4 LOAD_CONST    1 (0) 
       6 BINARY_TRUE_DIVIDE 
       8 POP_TOP 

    4   10 LOAD_CONST    2 ('All fine!') 
      12 STORE_NAME    0 (text_template) 
      14 POP_BLOCK 
      16 JUMP_FORWARD   38 (to 56) 

    5  >> 18 DUP_TOP 
      20 LOAD_NAME    1 (ZeroDivisionError) 
      22 COMPARE_OP    10 (exception match) 
      24 POP_JUMP_IF_FALSE  54 
      26 POP_TOP 
      28 STORE_NAME    2 (exc) 
      30 POP_TOP 
      32 SETUP_FINALLY   10 (to 44) 

    6   34 LOAD_CONST    3 ('Got exception: {exc.__class__.__name__}') 
      36 STORE_NAME    0 (text_template) 
      38 POP_BLOCK 
      40 POP_EXCEPT 
      42 LOAD_CONST    4 (None) 
     >> 44 LOAD_CONST    4 (None) 
      46 STORE_NAME    2 (exc) 
      48 DELETE_NAME    2 (exc) 
      50 END_FINALLY 
      52 JUMP_FORWARD    2 (to 56) 
     >> 54 END_FINALLY 
     >> 56 LOAD_CONST    4 (None) 
      58 RETURN_VALUE 
+0

Demontaż tego jest całkiem jasny (wyrzuć go w funkcji, uruchom 'dis.dis (f)') - dodaje zasadniczo 'exc = None; del exc' na końcu bloku 'finally' –

+1

@AnthonySottile Great point. Dodam to do mojej odpowiedzi. –