'finally'는 항상 Python에서 실행됩니까?


126

Python에서 가능한 모든 try-finally 블록에 대해 finally 블록이 항상 실행 됩니까?

예를 들어, except블록 에있는 동안 돌아 왔다고 가정 해 보겠습니다 .

try:
    1/0
except ZeroDivisionError:
    return
finally:
    print("Does this code run?")

아니면 다시 올릴 수도 있습니다 Exception.

try:
    1/0
except ZeroDivisionError:
    raise
finally:
    print("What about this code?")

테스트 결과 finally 위의 예에서 실행되는 것으로 나타 났지만 생각하지 못한 다른 시나리오가 있다고 생각합니다.

finally블록이 Python에서 실행되지 않을 수 있는 시나리오가 있습니까?


18
내가 상상할 수있는 유일한 경우는 finally실행에 실패하거나 "목적을 무너 뜨리는"경우가 무한 루프 sys.exit또는 강제 인터럽트 동안입니다. 문서의 상태를 finally항상 실행됩니다, 그래서 나는 그와 함께 갈 것입니다.
Xay

1
약간의 측면 적 사고와 당신이 요청한 것이 확실하지 않지만 작업 관리자를 열고 프로세스를 종료 finally하면 실행되지 않을 것이라고 확신합니다. D : 또는 같은 컴퓨터 전에 충돌하는 경우
알레한드로

144
finally전원 코드가 벽에서 찢어지면 실행되지 않습니다.
user253751 2013

3
C #에 대한 동일한 질문에 대한이 답변에 관심이있을 수 있습니다. stackoverflow.com/a/10260233/88656
Eric Lippert

1
빈 세마포어에서 차단하십시오. 신호를 보내지 마십시오. 끝난.
Martin James

답변:


206

"Guaranteed"는 어떤 구현에 해당하는 것보다 훨씬 강력한 단어 finally입니다. 무엇을 보장하는 것은 실행이 전체의 흘러 경우이다 try- finally구조, 그것이 통과하게 finally그렇게 할 수 있습니다. 보장되지 않는 것은 실행이 try- finally.

  • finally생성기 또는 비동기 코 루틴의 A 는 객체가 결론에 도달하지 않으면 절대 실행되지 않을 수 있습니다 . 일어날 수있는 방법은 많습니다. 여기에 하나가 있습니다.

    def gen(text):
        try:
            for line in text:
                try:
                    yield int(line)
                except:
                    # Ignore blank lines - but catch too much!
                    pass
        finally:
            print('Doing important cleanup')
    
    text = ['1', '', '2', '', '3']
    
    if any(n > 1 for n in gen(text)):
        print('Found a number')
    
    print('Oops, no cleanup.')

    이 예제는 약간 까다 롭습니다. 생성기가 가비지 수집되면 Python finallyGeneratorExit예외 를 던져 블록 을 실행하려고 시도 하지만 여기서 예외를 포착 한 다음 yield다시 한 번 Python이 경고를 출력합니다 ( "generator ignore GeneratorExit ") 포기합니다. 자세한 내용은 PEP 342 (향상된 생성기를 통한 코 루틴) 를 참조하십시오.

    생성기 또는 코 루틴이 결론을 내리기 위해 실행되지 않을 수있는 다른 방법으로는 객체가 GC 처리되지 않은 경우 (예, CPython에서도 가능) 또는 async with awaits가 __aexit__인 경우 또는 객체 가 블록 에서 awaits 또는 yields 인 경우가 finally있습니다. 이 목록은 완전한 것이 아닙니다.

  • finally데몬이 아닌 모든 스레드가 먼저 종료되면 데몬 스레드의 A 가 실행되지 않을 수 있습니다 .

  • os._exitfinally블록 을 실행하지 않고 즉시 프로세스를 중지합니다 .

  • os.forkfinally블록이 두 번 실행될 수 있습니다 . 두 번 발생하는 상황에서 예상 할 수있는 일반적인 문제뿐만 아니라 공유 리소스에 대한 액세스가 올바르게 동기화 되지 않은 경우 동시 액세스 충돌 (충돌, 중단 등)이 발생할 수 있습니다 .

    때문에 multiprocessing사용이 포크-없이-간부 작업자 프로세스를 생성하기 위해 사용하는 경우 포크의 시작 방법 다음 (유닉스의 기본)를 호출합니다 os._exit작업자의 작업이 완료되면 작업자에, finally그리고 multiprocessing상호 작용 문제 (수 있습니다 ).

  • C 레벨 분할 오류는 finally블록 실행 을 방해 합니다.
  • kill -SIGKILLfinally블록이 실행되는 것을 방지 합니다. SIGTERMSIGHUP 또한 방지 할 수 finally당신이 종료 자신을 제어 할 수있는 처리기를 설치하지 않으면 실행 블록; 기본적으로 Python은 SIGTERM또는 SIGHUP.
  • 의 예외로 finally인해 정리가 완료되지 않을 수 있습니다. 사용자 히트 제어-C을 경우 하나 특히 주목할만한 경우는 단지 우리가 실행을 시작하고 같은 finally블록을. 파이썬은KeyboardInterruptfinally 블록 내용의 모든 줄을 건너 뜁니다 . ( KeyboardInterrupt-안전한 코드는 작성하기가 매우 어렵습니다).
  • 컴퓨터의 전원이 꺼 지거나 최대 절전 모드에서 깨어나지 않으면 finally블록이 실행되지 않습니다.

finally블록은 거래 시스템이 아닙니다; 원 자성 보장이나 그 어떤 것도 제공하지 않습니다. 이러한 예 중 일부는 분명해 보일 수 있지만 이러한 일이 발생할 수 있다는 사실을 잊고 finally너무 많이 의존하기 쉽습니다 .


14
나는 당신의 목록의 첫 번째 요점이 정말로 관련이 있다고 믿고 그것을 피할 수있는 쉬운 방법이 있습니다. 1) bare를 사용하지 말고 생성기 내부를 except잡지 마십시오 GeneratorExit. 스레드 / 프로세스 종료 / 세그 폴팅 / 전원 끄기에 대한 요점은 예상되며 파이썬은 마술을 할 수 없습니다. 또한 예외 finally는 분명히 문제이지만 제어 흐름 finally 블록 으로 이동 되었다는 사실을 변경하지는 않습니다 . 과 관련하여 Ctrl+C이를 무시하는 신호 처리기를 추가하거나 현재 작업이 완료된 후 단순히 완전 종료를 "예약"할 수 있습니다.
Giacomo Alzetta

8
kill -9에 대한 언급 은 기술적으로 정확하지만 약간 불공평합니다. 어떤 언어로 작성된 프로그램도 kill -9를 받으면 코드를 실행하지 않습니다. 사실, 어떤 프로그램은 지금까지 받지 는, 그것은 아무것도 실행할 수 없습니다 싶어 이렇게해도 전혀 킬 -9. 그것이 킬 -9의 요점입니다.
Tom

10
@Tom : 요점은 kill -9언어를 지정하지 않았습니다. 솔직히 말해서 사각 지대에 있기 때문에 반복이 필요합니다. 너무 많은 사람들이 그들의 프로그램이 정리조차 허용되지 않고 그 궤도에서 정지 될 수 있다는 사실을 잊거나 깨닫지 못합니다.
cHao

5
@GiacomoAlzetta : finally거래 보장을 제공하는 것처럼 블록에 의존하는 사람들이 있습니다 . 그렇지 않다는 것이 분명해 보일 수 있지만 모든 사람이 깨닫는 것은 아닙니다. 발전기 케이스에 관해서는, 발전기 전혀 GC'ed되지 않을 수 있습니다 많은 방법 및 발전기 또는 코 루틴 실수 후 얻을 수있는 많은 방법이 있습니다 GeneratorExit, 심지어는 잡을하지 않는 경우 GeneratorExit, 예를 들어이는 IF async with의 중단 의 코 루틴 __exit__.
user2357112 모니카 지원

2
@ user2357112 예-개발자가 앱 시작시 임시 파일 등을 정리하도록 수십 년 동안 노력해 왔습니다. 소위 '정리 및 우아한 종료'에 의존하여 실망과 눈물을 요구합니다.)
Martin James

68

예. 마지막으로 항상 승리합니다.

이를 무력화하는 유일한 방법은 finally:실행할 기회를 얻기 전에 실행을 중지 하는 것입니다 (예 : 인터프리터 충돌, 컴퓨터 끄기, 생성기 영구 정지).

내가 생각하지 못한 다른 시나리오가 있다고 생각합니다.

생각하지 못했던 몇 가지 더 있습니다.

def foo():
    # finally always wins
    try:
        return 1
    finally:
        return 2

def bar():
    # even if he has to eat an unhandled exception, finally wins
    try:
        raise Exception('boom')
    finally:
        return 'no boom'

인터프리터를 종료하는 방법에 따라 마지막으로 "취소"할 수 있지만 다음과 같지는 않습니다.

>>> import sys
>>> try:
...     sys.exit()
... finally:
...     print('finally wins!')
... 
finally wins!
$

불안정한 사용 os._exit(제 생각에는 "통역사 충돌"에 해당) :

>>> import os
>>> try:
...     os._exit(1)
... finally:
...     print('finally!')
... 
$

저는 현재이 코드를 실행하여 우주의 열사 후에도 마침내 실행될 것인지 테스트하고 있습니다.

try:
    while True:
       sleep(1)
finally:
    print('done')

하지만 아직 결과를 기다리고 있으니 나중에 여기에서 다시 확인하세요.


5
또는 try catch에서 i 유한 루프가있는 경우
sapy

8
finally생성기 또는 코 루틴 에서 "통역사 충돌"조건 근처에 가지 않고도 실행에 실패 할 수 있습니다.
user2357112 모니카 지원

27
우주 시간의 열사가 사라진 후에 sleep(1)는 분명히 정의되지 않은 행동을 초래할 것입니다. :-D
David Foerster

_os.exit를 "파괴하는 유일한 방법은 컴파일러를 충돌시키는 것"바로 뒤에 언급 할 수 있습니다. 지금은 드디어 승리하는 사례가 섞여 있습니다.
Stevoisiak

2
@StevenVascellaro 나는 그것이 필요하다고 생각하지 않습니다- os._exit모든 실용적인 목적을 위해 충돌을 유발하는 것과 동일합니다 (불분명 한 출구). 종료하는 올바른 방법은 sys.exit입니다.
wim

9

Python 문서 에 따르면 :

이전에 무슨 일이 있었는지에 관계없이 코드 블록이 완료되고 발생한 예외가 처리되면 최종 블록이 실행됩니다. 예외 처리기 또는 else- 블록에 오류가 있고 새로운 예외가 발생하더라도 최종 블록의 코드는 계속 실행됩니다.

또한 finally 블록에있는 문을 포함하여 여러 개의 return 문이있는 경우 finally 블록 반환이 실행되는 유일한 문이라는 점에 유의해야합니다.


8

글쎄, 예 그리고 아니오.

보장되는 것은 Python이 항상 finally 블록을 실행하려고한다는 것입니다. 블록에서 복귀하거나 포착되지 않은 예외를 발생시키는 경우, 실제로 예외를 반환하거나 발생하기 직전에 finally 블록이 실행됩니다.

(귀하의 질문에있는 코드를 실행하여 스스로 제어 할 수 있었던 것)

finally 블록이 실행되지 않는 유일한 경우는 Python 인터프리터 자체가 예를 들어 C 코드 내부 또는 정전으로 인해 충돌하는 경우입니다.


하 하 .. 또는 try catch에 무한 루프가 있습니다
sapy

나는 "글쎄, 예, 아니오"가 가장 정확하다고 생각합니다. 마지막 : "항상"은 인터프리터가 실행할 수 있고 "finally :"에 대한 코드를 계속 사용할 수 있음을 의미하며 "승리"는 인터프리터가 finally : 블록을 실행하려고 시도하고 성공할 것으로 정의됩니다. 그것은 "예"이고 매우 조건 적입니다. "아니오"는 인터프리터가 "마지막으로"전에 중지 할 수있는 모든 방법입니다.-정전, 하드웨어 오류, 인터프리터를 겨냥한 kill -9, 인터프리터 또는 코드 오류, 인터프리터를 중단하는 다른 방법. 그리고 "finally :"안에 매달리는 방법.
Bill IV

1

생성기 함수를 사용하지 않고 이것을 찾았습니다.

import multiprocessing
import time

def fun(arg):
  try:
    print("tried " + str(arg))
    time.sleep(arg)
  finally:
    print("finally cleaned up " + str(arg))
  return foo

list = [1, 2, 3]
multiprocessing.Pool().map(fun, list)

수면은 일관성없는 시간 동안 실행될 수있는 코드 일 수 있습니다.

여기서 일어나는 것처럼 보이는 것은 완료하는 첫 번째 병렬 프로세스가 try 블록을 성공적으로 떠나지 만 함수에서 아무데도 정의되지 않은 값 (foo)을 반환하려고 시도하는 것입니다. 이로 인해 예외가 발생합니다. 이 예외는 다른 프로세스가 finally 블록에 도달하는 것을 허용하지 않고 맵을 죽입니다.

또한 bar = bazztry 블록에서 sleep () 호출 바로 뒤에 줄을 추가하면 . 그런 다음 해당 라인에 도달하는 첫 번째 프로세스는 예외 (bazz가 정의되지 않았기 때문에)를 발생시켜 자체 finally 블록이 실행되도록 한 다음 맵을 종료하여 다른 try 블록이 finally 블록에 도달하지 않고 사라지게합니다. 반환 문에 도달하지 않는 첫 번째 프로세스도 마찬가지입니다.

이것이 Python 다중 처리에서 의미하는 것은 프로세스 중 하나라도 예외가있을 수있는 경우 모든 프로세스에서 리소스를 정리하는 예외 처리 메커니즘을 신뢰할 수 없다는 것입니다. 다중 처리 맵 호출 외부에서 추가 신호 처리 또는 리소스 관리가 필요합니다.


-2

몇 가지 예와 함께 작동 방식을 확인하는 데 도움이되는 수락 된 답변에 대한 부록 :

  • 이:

     try:
         1
     except:
         print 'except'
     finally:
         print 'finally'

    출력됩니다

    드디어

  •    try:
           1/0
       except:
           print 'except'
       finally:
           print 'finally'

    출력됩니다


    마지막을 제외하고


1
이것은 질문에 전혀 대답하지 않습니다. 그것은 단지 그것이 작동 하는 때의 예를 가지고 있습니다 .
zixuan
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.