Python入門
第14回.関数内関数(関数のネスト)とスコープ

Pythonの初心者向け入門解説、人気のプログラミング言語Python
最終更新日:2020-09-20

第14回.関数内関数(関数のネスト)とスコープ


関数は一連の処理をまとめることで再利用可能にした、プログラム内の小さなプログラムのようなものです。
Pythonでの関数の記述方法は多彩です。
今回は、関数内関数とスコープについて解説します。


関数内関数は関数のネストです。
また、関数を使う上ではスコープについてもしっかり理解しておく必要があります。

関数の定義についての基本は以下を参照してください。
第13回.関数の定義(def文)と引数
関数は一連の処理をまとめることで再利用可能にしたものです。プログラム内の小さなプログラムと言えるものです。関数は引数を受け取り(引数が無い場合もあります)一連の処理をした結果を返します。Pythonには組み込み関数がありますが、このような関数を独自に作成することができます。


目次

関数内関数(関数のネスト)

def文の関数定義の中に、さらにdef文で関数を定義できます。
def文をネスト出来るという事です。

def 外側の関数():
    def 内側の関数():
        内側の関数の処理
    ・・・
    内側の関数の呼び出し
    外側の関数の処理

関数の中には複数の関数を定義できます。
さらに内側の関数の中にも関数を入れることもできます。
外側の関数では内側の関数を普通に呼び出して使います。
記述についてはこれだけです。
難しいものではありません。

関数内関数はこれを使えば便利な場合があるというものであり、使わなければ出来ない処理があるというものではありません。
どのような場面で使えば良いかは一概に言えるものではないと思います。
実践の中で、徐々に使いこなせれば良いと思います。
Pythonにはクロージャというものがあります。
これについては後々説明しますが、
クロージャは、この関数内関数の応用になります。

以下の例は極めて単純化したものです。

def fn():
    return "Python def"
print(fn())
このような関数とその呼び出しがあったとして、これをまとめて1つの関数にします。

def fn_outer():
    def fn_inner():
        return "Python def"
    print(fn_inner())
fn_outer()

モジュールトップレベルのメインの処理では、printを意識する必要がなくなり、関数を呼ぶだけに変わっています。
そもそも関数とは、一連の処理をまとめることで再利用可能にしたものです。
関数の作成では、このまとめる単位をどうするかが問題になります。
まとめる大きな単位の中に、小さい単位でまとめたものを入れることが出来る、それが関数内関数です。

少し悩ましいのはスコープだと思います。
内側の関数は、外側の関数の外からは呼び出すことができません。
これは、関数内で定義した変数を外から参照出来ない事と同じです。
スコープについては次節で詳しく解説します。

Python 関数内関数 関数のネスト スコープ


スコープとは

スコープとは、可視範囲・有効範囲・適用範囲の事です。
Pythonのスコープには以下の4つがあります。

・ローカルスコープ(Local scope)
・エンクロージングスコープ(Enclosing scope)
・グローバルスコープ(Global Scope)
・ビルトインスコープ(Built-in scope)

下に行くほど範囲が広く、より弱くなります。
上に行くほど範囲が狭く、より強くなります。

変数の適用順序
変数が参照されたとき、より上の狭いスコープから順に探されます。
ローカル→エンクロージング→グローバル→ビルトイン
この順で探していき、見つかった時点の変数が適用されます。

一般的にプログラミングでは、スコープはより狭くした方が良いとされています。


ローカルスコープ(Local scope)

関数内で定義された変数はローカル変数と呼ばれます。
ローカル変数は、定義した関数内でしか使用できません。
つまり、スコープはローカルに限定されているという事です。

他の関数や呼び出し元からは、関数内の変数を参照することができません。
関数が違えば、同じ変数名であっても、それぞれ完全に独立した変数になります。

Python 関数内関数 関数のネスト スコープ

関数fn1のローカル変数vは、他の関数からも呼び出し元からも参照できません。

エンクロージングスコープ(Enclosing scope)

エンクロージングスコープとは、関数がネストされている場合に内側の関数から見た1つ外側の関数のローカルスコープです。
エンクロージング変数は、内側の関数から参照はできますが、そのままでは書き換えることはできません。
エンクロージング変数を書き換えるには、nonlocal文でその変数を定義する必要があります。
※Visual Studioでは、nonlocalがあると対話モードへの一括で貼り付けでエラーになってしまいます。

def fn():
    def fn_inner():
        print(s)
    s = "Enclosing scope"
    fn_inner()
fn()
Python 関数内関数 関数のネスト スコープ

内側の関数fn_innerで外側の関数fnの変数sを参照できています。

def fn():
    def fn_inner():
        s = "write Enclosing"
        print(s)
    s = "Enclosing scope"
    fn_inner()
    print(s)
fn()
Python 関数内関数 関数のネスト スコープ

内側の関数fn_innerで外側の関数fnの変数sを書き換えるつもりが、
これでは、新たに内側の関数でローカル変数を定義したことになってしまいます。

def fn():
    def fn_inner():
        print(s) #エンクロージング変数を参照
        s = "write Enclosing" #これはエラー
        print(s)
    s = "Enclosing scope"
    fn_inner()
    print(s)
fn()

Python 関数内関数 関数のネスト スコープ

先にエンクロージング変数を参照すると、その後では同名の変数は定義することができません。

def fn():
    def fn_inner():
        nonlocal s #ローカル変数ではない
        s = "write Enclosing"
        print(s)
    s = "Enclosing scope"
    fn_inner()
    print(s)
fn()
Python 関数内関数 関数のネスト スコープ

内側の関数fn_innerで外側の関数fnの変数sを書き換えできています。

ただしスコープを安易に広げてしまう事は危険です。
多用すべきではなく、使用する場合は慎重になるべきだと思います。

グローバルスコープ(Global Scope)

グローバルとは、モジュール全体という事です。
グローバルスコープとは、モジュールのトップレベルのスコープです。
グローバル変数とは、モジュールのトップレベルで定義された変数です。

グローバル変数は、モジュール内のどの関数からでも参照できますが、そのままでは書き換えることはできません。
グローバル変数を書き換えるには、global文でその変数を定義する必要があります。
※Visual Studioでは、globalがあると対話モードへの一括で貼り付けでエラーになってしまいます。

def fn():
    def fn_inner():
        print(s)
    fn_inner()
    print(s)
s = "Global scope"
fn()
Python 関数内関数 関数のネスト スコープ

関数内でも、さらにその中の関数でも、グローバル変数sを参照できています。

def fn():
    s = "write Global"
    print(s)
s = "Global scope"
fn()
print(s)
Python 関数内関数 関数のネスト スコープ

関数内でグローバル変数sを書き換えるつもりが、
これでは、新たに関数内でローカル変数を定義したことになってしまいます。

def fn():
    print(s)
    s = "write Global"
    print(s)
s = "Global scope"
fn()
print(s)
Python 関数内関数 関数のネスト スコープ

先にグローバル変数を参照すると、その後では同名の変数は定義することができません。

def fn():
    global s #グローバル変数であると宣言
    s = "write Global"
    print(s)
s = "Global scope"
fn()
print(s)
Python 関数内関数 関数のネスト スコープ

関数fnの中でグローバル変数sを書き換えできています。

ただしスコープを安易に広げてしまう事は危険です。
多用すべきではなく、使用する場合は慎重になるべきだと思います。

ビルトインスコープ(Built-in scope)

どこからでも参照できるスコープです。
組み込み関数は、ビルトインスコープです。
Pythonには数多くの関数と型が組み込まれており、様々な処理を行うことができます。組み込み関数は、Python入門の中でもすでにいくつか使用していますし、これからも頻繁に使用していきます。全部で69個あります。
また、True、False、None等の組み込み定数もビルトインスコープです。
特に意識することなく使用しているものです。


ミュータブルのコンテナオブジェクトの値は書き換えられる

ここまでは、以下のように書きました。
エンクロージング変数は、内側の関数から参照はできますが、そのままでは書き換えることはできません。
グローバル変数は、モジュール内のどの関数からでも参照できますが、そのままでは書き換えることはできません。

しかしこれは、変数を書き換え(置き換え)出来ないという事です。
変数がミュータブルのコンテナオブジェクトの場合は、その要素の値は書き換えができます。

Python 関数内関数 関数のネスト スコープ

変数がイミュータブルでも、
その要素として入っているミュータブルのコンテナオブジェクトは同様に書き換えできます。

Python 関数内関数 関数のネスト スコープ




同じテーマ「Python入門」の記事

第11回.辞書(dict型)
第12回.組み込み関数一覧
第13回.関数の定義(def文)と引数
第14回.関数内関数(関数のネスト)とスコープ
第15回.lambda(ラムダ式、無名関数)と三項演算子
第16回.Pythonの引数は参照渡しだが・・・
第17回.リスト内包表記
第18回.例外処理(try文)とexception一覧
第19回.import文(パッケージ・モジュールのインポート)
第20回.フォルダとファイルの一覧を取得(os,glob,pathlib)
第21回.CSV読み込みとopen()関数とwith文


新着記事NEW ・・・新着記事一覧を見る

WEBスクレイピング(selenium)|Python入門(10月11日)
エクセルを操作する(pywin32:win32com)|Python入門(10月5日)
エクセルを操作する(openpyxl)|Python入門(10月3日)
pipコマンド(外部ライブラリのインストール)|Python入門(10月1日)
CSV読み書き(csvモジュール)|Python入門(9月29日)
「Excel 4.0 マクロ」の使い方|VBA技術解説(9月28日)
CSV読み込みとopen()関数とwith文|Python入門(9月28日)
フォルダとファイルの一覧を取得(os,glob,pathlib)|Python入門(9月26日)
import文(パッケージ・モジュールのインポート)|Python入門(9月24日)
例外処理(try文)とexception一覧|Python入門(9月23日)


アクセスランキング ・・・ ランキング一覧を見る

1.最終行の取得(End,Rows.Count)|VBA入門
2.RangeとCellsの使い方|VBA入門
3.変数宣言のDimとデータ型|VBA入門
4.セルのコピー&値の貼り付け(PasteSpecial)|VBA入門
5.マクロって何?VBAって何?|VBA入門
6.Range以外の指定方法(Cells,Rows,Columns)|VBA入門
7.繰り返し処理(For Next)|VBA入門
8.セルに文字を入れるとは(Range,Value)|VBA入門
9.とにかく書いてみよう(Sub,End Sub)|VBA入門
10.マクロはどこに書くの(VBEの起動)|VBA入門




このサイトがお役に立ちましたら「シェア」「Bookmark」をお願いいたします。


記述には細心の注意をしたつもりですが、
間違いやご指摘がありましたら、「お問い合わせ」からお知らせいただけると幸いです。
掲載のVBAコードは動作を保証するものではなく、あくまでVBA学習のサンプルとして掲載しています。
掲載のVBAコードは自己責任でご使用ください。万一データ破損等の損害が発生しても責任は負いません。



このサイトがお役に立ちましたら「シェア」「Bookmark」をお願いいたします。
本文下部へ