関数がネストされたPythonコードで関数の内側で変更した変数を関数の外側から参照する方法を調べてみた

2020.01.07

こんにちは、CX事業本部の若槻です。

関数がネストされたPythonコードで、関数の内側で変更(定義)した変数の値を外側から参照したい場合があります。

例えば以下のコードでは、inner()の外側(outer()の内側)でvar = 'Initial Var'とし、inner()の内側でvar = 'New Var'としています。

def outer():

    var = 'Initial Var'

    def inner():
        var = 'New Var'
        return(var)

    print(inner())
    print(var)

この場合outer()を実行した際の出力は

# 出力
>> python outer()
New Var
Initial Var

となり、inner()の内側で行った変更が外側に反映されず、print(inner())print(var)の出力結果が異なる結果となりました。これは、varのスコープがinner()およびouter()のそれぞれのローカルスコープであるためです。

今回は、このように関数がネストされたPythonコードで、関数の内側で変更(定義)した変数の値を外側から参照する方法(上記のようなコードでprint(var)の出力をNew Varとする方法)について調べてみました。

変更された値を関数の外側から参照可能とするパターン

以下の4パターンの方法が確認できました。

outer()内でvarを定義、inner()内およびouter()内でglobal宣言

inner()内およびouter()内でvarのglobal宣言(global var)を行う方法。

def outer():

    global var
    var = 'Initial Var'

    def inner():
        global var
        var = 'New Var'
        return(var)

    print(inner())
    print(var)
# 出力
New Var
New Var

varのスコープをinner()およびouter()でグローバルスコープとすることにより、それぞれの関数内から同じグローバルのvarを変更・参照させることができます。

参考

global 文は、現在のコードブロック全体で維持される宣言文です。 global 文は、列挙した識別子をグローバル変数として解釈するよう指定することを意味します。 global を使わずにグローバル変数に代入を行うことは不可能ですが、自由変数を使えばその変数をグローバルであると宣言せずにグローバル変数を参照することができます。

outer()内でvarを定義、inner()内でnonlocal宣言

varに対してinner()内でnonlocal宣言(nonlocal var)を行う方法。

def outer():

    var = 'Initial Var'

    def inner():
        nonlocal var
        var = 'New Var'
        return(var)

    print(inner())
    print(var)
# 出力
New Var
New Var

inner()内でnonlocal宣言をすることにより、inner()の外側のスコープ(=outer()のローカルスコープ)のvarの値を変更させることができます。

参考

nonlocal 文は、列挙された識別子がグローバルを除く一つ外側のスコープで先に束縛された変数を参照するようにします。これは、束縛のデフォルトの動作がまずローカル名前空間を探索するので重要です。この文は、中にあるコードが、グローバル (モジュール) スコープ以外のローカルスコープの外側の変数を再束縛できるようにします。

outer()外でvarを定義、inner()内でglobal宣言

outer()外でvarを定義して、inner()内でvarのglobal宣言(global var)を行う方法。

var = 'Initial Var'

def outer():

    def inner():
        global var
        var = 'New Var'
        return(var)

    print(inner())
    print(var)
# 出力
New Var
New Var

outer()外で定義されたvarはグローバルスコープとなります。print(var)実行時にouter()はローカルスコープ→グローバルスコープの順でvarを検索するため、inner()内で変更されたグローバルスコープのvarの値がprint(var)では出力されます。

参考

Python では、関数の中からしか参照されていない変数は暗黙的にグローバルとされます。 関数の本体のどこかで値が変数に代入されたなら、明示的にグローバルであると宣言されない限り、ローカルであるとみなされます。

inner()内でglobal宣言

inner()内でvarのglobal宣言(global var)を行う方法。outer()内でvarの定義は行わない。

def outer():

    def inner():
        global var
        var = 'New Var'
        return(var)

    print(inner())
    print(var)
# 出力
New Var
New Var

outer()はローカルスコープ→グローバルスコープの順でvarを検索するため、inner()内で変更されたグローバルスコープのvarの値がprint(var)では出力されます。

変更された値を関数の外側から参照できないパターン

参照できないパターンについても記載します。

inner()内のみでglobal宣言

def outer():

    var = 'Initial Var'

    def inner():
        global var
        var = 'New Var'
        return(var)

    print(inner())
    print(var)
# 出力
New Var
Initial Var

inner()内でグローバルスコープのvarが定義されていますが、outer()内ではローカルスコープのvarが定義されているため、outer()内ではローカルスコープのvarが優先して参照されます。

inner()内でnonlocal宣言

def outer():

    def inner():
        nonlocal var
        var = 'New Var'
        return(var)

    print(inner())
    print(var)
# 出力
[ERROR] Runtime.UserCodeSyntaxError: Syntax error in module 'module': no binding for nonlocal 'var' found (module.py, line 4)

inner()内でvarのnonlocal宣言をしても、関数の外側に変更できるvarがない場合はエラーとなります。

まとめ

  • 関数内で変更された変数を関数外で利用したい場合は関数内でglobal宣言かnonlocal宣言を行う。
  • 変数の参照時にはグローバルスコープ→ローカルスコープの順で検索が行われる。

おわりに

今回ご紹介した内容は、outer()をハンドラー、inner()をハンドラー内の関数とすれば、AWS LambdaのPythonコードにもそのまま当てはめることができます。参考になれば幸いです。

以上