🤔

Pythonの例外処理ブロックでreturnを行ったときの挙動

2024/09/28に公開

検証内容

Python3において、try/except/else/finally文中でreturnを記述した時の挙動と、例外を入れ子にした時の挙動をprintとreturnを使って検証する。

printが表示されれば、どのブロックで処理が実行されるかとその順番がわかる。

returnは関数中で1度しか実行されないので、どのブロックで優先的にreturnが起きるかがわかる。

例外無し

まずは、普通の例外処理の動きを見てみる。

try/except/else/finallyブロック内でreturnを行わず、例外が発生しなければ

  1. tryブロックでprint実行

  2. elseブロックでprint実行

  3. finallyブロックでprint実行

  4. 例外処理ブロックの外でprint実行

  5. 例外処理ブロックの外でreturn

というように、exceptブロックには入らず、上から順番に実行されていく。

def non_raise_except():
    try:
        print("print in try-block")
    except:
        print("print in except-block")
    else:
        print("print in else-block")
    finally:
        print("print in finally-block")
    print("print outside try-except-else-finally-block")
    return "return outside try-except-else-finally-block"
print in try-block
print in else-block
print in finally-block
print outside try-except-else-finally-block
return outside try-except-else-finally-block

try-blockで例外発生

try/except/else/finallyブロック内でreturnを行わず、tryブロック内で例外が発生すると

  1. tryブロックでprint実行

  2. exceptブロックでprint実行

  3. finallyブロックでprint実行

  4. 例外処理ブロックの外でprint実行

  5. 例外処理ブロックの外でreturn

というように、elseブロックには入らず、上から順番に実行されていく。

def raise_except_in_try():
    try:
        print("print in try-block")
        raise Exception("Exception in try-block")
    except:
        print("print in except-block")
    else:
        print("print in else-block")
    finally:
        print("print in finally-block")
    print("print outside try-except-else-finally-block")
    return "return outside try-except-else-finally-block"
print in try-block
print in except-block
print in finally-block
print outside try-except-else-finally-block
return outside try-except-else-finally-block

try/except/else-block内でreturn

try/except/elseブロック内とその外側でreturnを記述した場合、例外が発生しなければ

  1. tryブロックでprint実行

  2. finallyブロックでprint実行

  3. tryブロックでreturn

というように、tryブロックでreturnされる前にfinallyブロックの処理が実行される。

def return_in_try():
    try:
        print("print in try-block")
        return "return in try-block"
    except:
        print("print in except-block")
        return "return in except-block"
    else:
        print("print in else-block")
        return "return in else-block"
    finally:
        print("print in finally-block")
    print("print outside try-except-else-finally-block")
    return "return outside try-except-else-finally-block"
print in try-block
print in finally-block
return in try-block

finally-block内でreturn

try/except/else/finallyブロック内とその外側でreturnを記述した場合、例外が発生しなければ

  1. tryブロックでprint実行

  2. finallyブロックでprint実行

  3. finallyブロックでreturn実行

というように、returnされる前にfinallyブロックが実行され、returnはfinallyブロックから返される。

def return_in_finally():
    try:
        print("print in try-block")
        return "return in try-block"
    except:
        print("print in except-block")
        return "return in except-block"
    else:
        print("print in else-block")
        return "return in else-block"
    finally:
        print("print in finally-block")
        return "return in finally-block"
    print("print outside try-except-else-finally-block")
    return "return outside try-except-else-finally-block"
print in try-block
print in finally-block
return in finally-block

ネストした例外処理のfinally-block内でreturn

例外処理をネストしてそれぞれのtry/except/else/finallyブロック内でreturnを記述した場合、

  1. 親tryブロックでprint実行

  2. 子tryブロックでprint実行

  3. 子finallyブロックでprint実行

  4. 親finallyブロックでprint実行

  5. 親finallyブロックでreturn

というように、親と子のtryブロックの処理が実行された後、子のfinallyブロックが実行され、最終的に親のfinallyブロックからreturnされる。

def return_in_finally_nest():
    try:
        print("print in try-block")

        try:
            print("print in child-try-block")
            return "return in child-try-block"
        except:
            print("print in child-except-block")
            return "return in child-except-block"
        else:
            print("print in child-else-block")
            return "return in child-else-block"
        finally:
            print("print in child-finally-block")
            return "return in child-finally-block"

        return "return in try-block"
    except:
        print("print in except-block")
        return "return in except-block"
    else:
        print("print in parent-else-block")
        return "return in parent-else-block"
    finally:
        print("print in parent-finally-block")
        return "return in parent-finally-block"
print in try-block
print in child-try-block
print in child-finally-block
print in parent-finally-block
return in parent-finally-block

まとめ

  • returnはfinally内に書かれたものが最優先

  • finallyにreturnが無く、try/except/else内にreturnがある場合、return直前にfinallyブロックの処理が実行される

  • ネストされた例外処理でfinally内にreturnが書かれている場合、親のfinallyブロックのreturnが優先される

「finallyにreturnを記述してはいけない」と言われるのは、try/except/elseブロックのreturnが機能せず、ネストした場合には、すべて親のfinallyブロックのreturnに吸収されるため。

また、公式ドキュメントで、finallyブロックでreturnが記述されている場合には、try/except/elseブロック内でraiseされた例外が機能しないと説明されている。

Discussion