Pythonのスコープ!初心者でも理解できる10の詳細ガイドライン – Japanシーモア

Pythonのスコープ!初心者でも理解できる10の詳細ガイドライン

Pythonスコープの理解を深めるための10の詳細ガイドPython
この記事は約23分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

このサービスは複数のSSPによる協力の下、運営されています。

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

Pythonのスコープについて混乱していますか?

スコープとは何か、なぜそれが重要なのかを理解することは、Pythonプログラミングの成功のための重要なステップです。

本ガイドでは、Pythonのスコープの基本から応用まで、初心者でも理解できる10の詳細ガイドを提供します。

●Pythonとスコープについて

○Pythonとは

Pythonは、シンプルで読みやすいコードを書くことを重視したプログラミング言語です。

そのため、初心者でも学びやすく、また多くの分野で使われています。

○スコープとは

スコープとは、変数が存在し、参照可能な範囲のことを指します。

変数の参照可能範囲を理解することは、コードの動作を理解するために重要です。

●Pythonのスコープの種類

Pythonのスコープは、主に次の4つのカテゴリーに分けられます。

○ローカルスコープ

ローカルスコープとは、特定の関数やメソッドの内部で定義された変数の範囲を指します。

ローカルスコープの変数は、その関数の中でのみアクセス可能です。

○エンクロージングスコープ

エンクロージングスコープとは、ネストされた関数の中で、直接囲まれている外側の関数のスコープを指します。

このスコープの変数は、内部のネストされた関数からアクセス可能です。

○グローバルスコープ

グローバルスコープとは、プログラム全体からアクセス可能な変数の範囲を指します。

通常、プログラムの最上位レベルで定義された変数は、グローバルスコープを持ちます。

○ビルトインスコープ

ビルトインスコープとは、Pythonの組み込み関数や変数が存在する範囲を指します。

これらの関数や変数は、Pythonプログラムのどこからでもアクセス可能です。

次に、これらのスコープを詳しく解説し、具体的なコード例を通じて理解を深めていきます。

●Pythonでスコープを理解するための10のサンプルコード

○サンプルコード1:ローカルスコープの基本

このコードでは、ローカルスコープを使って変数を操作する方法を紹介しています。

この例では、関数内で定義された変数がローカルスコープを持ち、関数の外からはアクセスできないことを表しています。

def my_function():
    local_var = "I'm local"
    print(local_var)

my_function()
try:
    print(local_var)
except NameError:
    print("local_var is not accessible outside of the function.")

このコードを実行すると、最初に “I’m local” が表示されます。これは関数内部からローカルスコープの変数にアクセスしているからです。

しかし、関数外部から同じ変数にアクセスしようとすると、「local_var is not accessible outside of the function.」というメッセージが表示されます。

これは、ローカル変数はその関数の中でしかアクセスできないことを表しています。

○サンプルコード2:エンクロージングスコープの基本

次にエンクロージングスコープについて考えてみましょう。

これは、ネストした関数、つまり、一つの関数が別の関数の内部に定義されている場合に現れます。

親関数のローカルスコープが、内部にある子関数から参照可能であるところが特徴です。

下記はエンクロージングスコープを示すPythonのサンプルコードです。

このコードでは、内部の関数innerが外部の関数outerのローカルスコープを参照しています。

def outer():
    outer_var = "アウター変数"
    def inner():
        print(outer_var)
    inner()

outer()

この例では、outer関数の中にinner関数が定義されており、inner関数はouter関数のローカル変数outer_varを参照しています。

したがって、inner関数はouter関数のローカルスコープ、すなわちエンクロージングスコープにアクセスしています。

このコードを実行すると、次のような結果が得られます。

アウター変数

これはinner関数がouter_varというローカル変数を参照した結果です。

この結果から、Pythonのエンクロージングスコープとは、ネストした関数が親関数のローカル変数を参照できる仕組みであることがわかります。

次に、エンクロージングスコープの特性をさらに理解するために、同じ変数名がローカルスコープとエンクロージングスコープで使用されている場合の挙動を見てみましょう。

def outer():
    var = "アウター変数"
    def inner():
        var = "インナー変数"
        print(var)
    inner()
    print(var)

outer()

このコードでは、outer関数とinner関数の両方でvarという名前の変数が定義されています。

しかし、inner関数の中では新たにvarが定義されているため、inner関数の中で参照されるvarは、inner関数のローカルスコープにある”インナー変数”になります。

一方、outer関数の中で参照されるvarは、そのローカルスコープにある”アウター変数”です。

このコードを実行すると、次のような結果が得られます。

インナー変数
アウター変数

この結果から、スコープのルールがどのように機能するかが見て取れます。

つまり、ローカルスコープが最優先され、その中で変数が見つからない場合に限り、エンクロージングスコープを参照することがわかります。

○サンプルコード3:グローバルスコープの基本

次に進む前に、Pythonのグローバルスコープについて説明します。

グローバルスコープは、スクリプト全体からアクセス可能なスコープです。

スクリプトのトップレベルに定義された変数や関数は、どこからでも参照できます。

ただし、関数内でその変数に新たな値を代入しようとすると、新しいローカルスコープの変数が作成されます。

それでは、次のコードを見てみましょう。

#グローバル変数を定義
x = 10

def print_x():
    #関数内でグローバル変数を参照
    print(x)

print_x()

このコードでは、xというグローバル変数を使って値10を設定し、print_xという関数を定義しています。

この関数はxを表示します。

最後にprint_x関数を呼び出すと、グローバル変数xの値が表示されます。

しかし、関数内でグローバル変数xに新しい値を代入しようとすると、どうなるでしょうか。

次のコードをご覧ください。

x = 10

def modify_x():
    x = 20
    print(x)

modify_x()
print(x)

このコードでは、modify_xという新しい関数を定義し、この関数内でxに新しい値20を代入しようとしています。

しかし、新しいローカル変数xが作成され、グローバル変数xは変更されません。

そのため、最初のprint(x)は20を表示し、次のprint(x)は元のグローバル変数xの値10を表示します。

このことから、関数内でグローバル変数を変更するためには、特殊なキーワードが必要であることが分かります。

それが「global」キーワードであり、これについては後の節で詳しく説明します。

ここでの重要なポイントは、Pythonがスコープをどのように扱っているか、またそれがコードの動作にどのように影響を与えるかを理解することです。

関数内での変数の扱い方は、プログラムの動作を大きく左右しますので、その特性を理解し、うまく活用することが求められます。

○サンプルコード4:ビルトインスコープの基本

Pythonのスコープの中で最も広範なスコープがビルトインスコープです。

ビルトインスコープはPythonの組み込み関数や変数が属するスコープで、全てのPythonコードからアクセス可能です。

例えば、print関数やlen関数などは、どのスコープからでもアクセスできます。

しかし、ビルトインスコープの名前を新しい値で上書きすることは可能です。

そのため、組み込み関数や変数の名前を自分の変数や関数で上書きしないよう注意が必要です。

それでは、ビルトインスコープについて見てみましょう。

次のコードは、組み込み関数lenを上書きする例です。

#組み込み関数lenを上書き
def len(x):
    return "Custom length function: " + str(x)

print(len("Hello, world!"))

このコードでは、組み込み関数lenを新しい関数で上書きしています。

新しいlen関数は引数を取り、その引数を含む文字列を返します。

これにより、Pythonの組み込み関数lenが自作の関数で上書きされ、元のlen関数は利用できなくなります。

この結果を受けて、ビルトインスコープの名前を注意深く扱うことが重要であることが理解できるでしょう。

特に、自分のコード内で新しい変数や関数を定義する際には、それが組み込み関数や変数の名前と衝突しないかを確認することが重要です。

このことを心に留めておくことで、思わぬバグの発生を避けることができます。

また、他の開発者があなたのコードを理解し、利用することも容易になります。

○サンプルコード5:関数内のスコープ

ここで、関数内のスコープ、つまりローカルスコープについて説明します。

ローカルスコープとは、関数内で定義された変数が属するスコープのことを指します。

このローカルスコープは、関数が呼び出されたときに作成され、関数が終了すると消滅します。

ローカルスコープ内で定義された変数は、その関数内でのみアクセス可能であり、関数外からはアクセスできません。

これにより、変数名の衝突を避けることができ、コードの見通しも良くなります。

ローカルスコープを示すサンプルコードを紹介します。

def my_function():
    local_var = "I'm local!"
    print(local_var)

my_function()

try:
    print(local_var)
except NameError:
    print("NameError has occurred.")

このコードでは、関数my_function内でローカル変数local_varを定義しています。

そして、この変数を関数内でprint関数により出力しています。

関数my_functionを呼び出すと、関数内で定義されたlocal_varが出力されます。

しかし、関数の外からlocal_varにアクセスしようとすると、NameErrorが発生します。

これは、local_varが関数my_functionのローカルスコープ内でしか存在せず、関数の外からは参照できないことを表しています。

○サンプルコード6:ネストした関数のスコープ

Pythonでは関数の中に更に関数を定義することができます。

これをネストした関数と呼びます。ネストした関数では、内側の関数が外側の関数のスコープにアクセスできる特性があります。

これをレキシカルスコープ、または静的スコープと呼びます。

ネストした関数のスコープについて説明するサンプルコードを紹介します。

def outer_function():
    outer_var = "I'm outer!"

    def inner_function():
        inner_var = "I'm inner!"
        print(outer_var)
        print(inner_var)

    inner_function()

outer_function()

このコードでは、外側の関数outer_functionの中に、内側の関数inner_functionが定義されています。

outer_functionでは変数outer_varを定義し、inner_functionでは変数inner_varを定義しています。

inner_function内で、outer_varとinner_varをprint関数により出力します。

outer_functionを呼び出すと、inner_functionが実行され、outer_varとinner_varが出力されます。

この結果は、inner_functionがouter_functionのスコープにアクセスできるためです。

inner_functionは、自分自身のスコープ(inner_varが定義されている)と外側のスコープ(outer_varが定義されている)の両方にアクセスすることができます。

つまり、Pythonでは内側の関数は外側の関数のローカル変数にアクセスできるが、その逆はできないということを理解しておきましょう。

○サンプルコード7:グローバルキーワードの使用

Pythonのグローバルキーワードを理解することは、Pythonのスコープを深く理解するために不可欠です。

グローバルキーワードは、関数内でグローバル変数を明示的に使用したい場合に使います。

それでは、具体的なコードを見てみましょう。

global_var = "I'm global!"

def some_function():
    global global_var
    global_var = "I'm changed!"
    print(global_var)

some_function()
print(global_var)

このコードでは、最初に変数global_varをグローバルスコープで定義し、その値を”I’m global!”としています。

次に、関数some_functionを定義しています。

関数内で、globalキーワードを使用してglobal_varをグローバル変数として宣言しています。

このglobalキーワードの使用により、some_function内でglobal_varの値を変更すると、グローバルスコープのglobal_varの値も変わります。

その結果、some_functionを呼び出すと、global_varの値が”I’m changed!”に変わり、その変更がsome_functionの外側でも反映されます。

このように、globalキーワードを使うと、関数内からグローバル変数を変更できます。

ただし、グローバル変数はプログラム全体からアクセスできるため、変更がプログラム全体に影響を及ぼす可能性があります。

そのため、グローバル変数の使用は注意が必要です。

○サンプルコード8:ノンローカルキーワードの使用

Pythonでは、関数内の関数(ネストした関数)が外側の関数の変数にアクセスするためには、nonlocalキーワードを使用します。nonlocalキーワードはPython3から導入されました。

これは、グローバルスコープではなく、直近の外側の関数のスコープ(エンクロージングスコープ)を参照します。

具体的なコードを見てみましょう。

def outer_function():
    outer_var = "I'm outer!"

    def inner_function():
        nonlocal outer_var
        outer_var = "I'm changed!"
        print(outer_var)

    inner_function()
    print(outer_var)

outer_function()

このコードでは、外側の関数outer_function内で変数outer_varを定義し、その値を”I’m outer!”としています。次に、inner_functionという内側の関数を定義しています。

この内側の関数内で、nonlocalキーワードを使用してouter_varを宣言しています。

このnonlocalキーワードの使用により、inner_function内でouter_varの値を変更すると、outer_functionのスコープのouter_varの値も変わります。

したがって、inner_functionを呼び出すと、outer_varの値が”I’m changed!”に変わり、その変更がouter_functionのスコープでも反映されます。

このように、nonlocalキーワードを使うと、ネストした関数から外側の関数の変数を変更できます。

この性質は、特定の状況下、特に再帰的な処理を行う際などに非常に有用です。

○サンプルコード9:スコープとクラス

Pythonのクラスもまた一つのスコープを形成します。

クラス内で定義された変数(属性)は、そのクラス内のすべてのメソッドからアクセスできます。

また、クラスメソッドやスタティックメソッド内でも、クラス変数にアクセスできます。

それでは、具体的なコードを見てみましょう。

class MyClass:
    class_var = "I'm class variable!"

    def method(self):
        print(self.class_var)

    @classmethod
    def class_method(cls):
        print(cls.class_var)

    @staticmethod
    def static_method():
        print(MyClass.class_var)

obj = MyClass()
obj.method()
MyClass.class_method()
MyClass.static_method()

このコードでは、MyClassというクラスを定義し、その中にclass_varというクラス変数を定義しています。

class_varはクラススコープに存在するため、クラスのどこからでもアクセスできます。

そして、methodというインスタンスメソッド、class_methodというクラスメソッド、static_methodというスタティックメソッドを定義しています。

これらのメソッドの中で、class_varにアクセスし、その値を出力しています。

このように、Pythonのクラス内では、クラス変数にどのメソッドからでもアクセスできるため、メソッド間で共有する情報を保持するのに便利です。

しかし、クラス外から直接アクセスすることは基本的には推奨されません。

それは、クラスの内部実装に依存するコードを書くことを意味し、後でクラスの実装が変わった場合にコードの修正が必要になるからです。

○サンプルコード10:スコープと例外処理

例外処理の中でもスコープは活用されます。

例外処理にはtry-except文が使われ、この中で発生した例外はローカルスコープとして扱われます。

例えば、tryブロック内で定義された変数は、exceptブロックでも参照可能です。

try:
    problematic_var = 100 / 0
except ZeroDivisionError:
    print(f"An error occurred but problematic_var is {problematic_var}")

このコードでは、tryブロック内でproblematic_varという変数を定義し、その次に0で割るというエラーを意図的に発生させています。

例外が発生した場合、Pythonは即座にexceptブロックに移行します。

そして、exceptブロック内では、tryブロックで定義したproblematic_varの値を出力しています。

これが可能なのは、tryブロックとexceptブロックが同じスコープ内にあるためです。

このように、try-except文の中では一つのスコープが形成されるため、例外処理の中でも変数の扱い方には注意が必要です。

例外が発生したときに特定の変数を参照したい場合は、その変数がtryブロックのスコープ内に存在することを確認する必要があります。

スコープの理解はプログラミング力を高める上で重要な要素であるため、例外処理の際にもスコープの仕組みを理解し、適切にコードを書くことが求められます。

●Pythonのスコープを利用した応用例

スコープの理解は、プログラムの複雑性を管理し、バグを防ぐための重要なツールです。

ここでは、Pythonのスコープを活用した応用例を紹介します。

特に、再帰関数とデコレータの作成にスコープは欠かせません。

○応用例1:スコープと再帰関数

再帰関数とは、自分自身を呼び出す関数のことを指します。

再帰関数を使うと、コードをシンプルにし、複雑な問題を解決することができます。

ただし、再帰関数を理解し、適切に使うためにはスコープの理解が必要です。

再帰関数では、それぞれの再帰呼び出しで新たなスコープが生成されます。

このスコープ内の変数は、その呼び出しに固有のもので、他の再帰呼び出しには影響を与えません。

再帰関数を用いたフィボナッチ数列の計算プログラムを紹介します。

def fib(n):
    if n <= 1:
       return n
    else:
       return (fib(n-1) + fib(n-2))

print(fib(10))

このコードでは、fibという関数を定義しています。

この関数は、引数として一つの整数nを受け取り、フィボナッチ数列のn番目の値を計算して返します。

この関数は自身を再帰的に呼び出しているため、各再帰呼び出しで新たなスコープが形成されます。

各スコープは独立しており、他のスコープに影響を与えません。

そのため、再帰的に処理を行うことができます。

このプログラムを実行すると、フィボナッチ数列の10番目の数、つまり55が出力されます。

○応用例2:デコレータとスコープ

デコレータは、他の関数を引数として受け取り、その動作を拡張または変更する関数です。

デコレータを理解するためには、スコープの概念が不可欠です。

デコレータ内で定義された関数は、そのデコレータのスコープ内で存在します。

これにより、デコレータ内で生成された変数を、デコレータが装飾する関数に引き継ぐことができます。

def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")

say_hello()

このコードでは、my_decoratorというデコレータを定義しています。

このデコレータは、引数として関数funcを受け取り、その前後にメッセージを表示する新たな関数wrapperを返します。

その後、say_helloという関数を定義し、これにmy_decoratorデコレータを適用しています。

その結果、say_hello関数が呼び出されると、その前後にデコレータによって定義されたメッセージが表示されます。

このプログラムを実行すると、次の出力が得られます。

Something is happening before the function is called.
Hello!
Something is happening after the function is called.

デコレータはスコープを用いて、関数の動作を柔軟に変更する強力なツールです。

しかし、デコレータを適切に使うためにはスコープの理解が必要です。

●Pythonのスコープの注意点と対処法

Pythonのスコープについて理解を深めることで、コードの読みやすさ、効率、保守性を高めることができます。

しかし、一方でスコープは複雑な問題を引き起こす可能性もあります。

Pythonのスコープに関する一般的な問題とその対処法について解説します。

○注意点1:グローバル変数とローカル変数

スコープは変数の有効範囲を定義しますが、関数内で定義された変数(ローカル変数)と関数外で定義された変数(グローバル変数)ではスコープが異なります。

x = 10  # グローバル変数

def my_func():
    x = 20  # ローカル変数
    print(x)  

my_func()
print(x)

このコードでは、xという名前の変数を二つ定義しています。

一つは関数外で、もう一つは関数my_funcの中で定義しています。

関数内で定義した変数はローカル変数となり、そのスコープはその関数内に限定されます。

一方、関数外で定義した変数はグローバル変数となり、プログラムのどこからでもアクセス可能です。

このプログラムを実行すると、次の出力が得られます。

20
10

まず、関数my_funcが呼び出され、関数内のprint(x)が実行されると、そのスコープ内に存在するローカル変数xの値20が表示されます。

次に、関数外でprint(x)が実行されると、グローバル変数xの値10が表示されます。

この例からわかるように、同じ名前のグローバル変数とローカル変数がある場合、その名前の変数にアクセスするとスコープに最も近い変数が使用されます。

このような名前の衝突は混乱を招く可能性があるため、可能な限り避けるべきです。

○対処法1:globalキーワードの使用

上記の問題を解決する一つの方法は、globalキーワードを使用することです。

globalキーワードを使用すると、関数内からグローバル変数を参照し、その値を変更することができます。

x = 10  # グローバル変数

def my_func():
    global x
    x = 20  # グローバル変数を変更
    print(x)

my_func()
print(x)

このコードでは、my_func関数内でglobal xと宣言しています。

これにより、関数内のxはグローバル変数を参照し、その値を変更します。

このプログラムを実行すると、次の出力が得られます。

20
20

関数my_funcが呼び出されると、グローバル変数xの値が20に変更され、その値が表示されます。

次に、関数外でprint(x)が実行されると、同じく変更されたグローバル変数xの値20が表示されます。

●Pythonのスコープのカスタマイズ方法

Pythonのスコープに関する理解を深めるためには、スコープを自由にカスタマイズする方法についても学ぶことが重要です。

ここでは、Pythonのnonlocalキーワードを使ったスコープのカスタマイズ方法を詳しく説明します。

○カスタマイズ例1:nonlocalキーワードの使用

Pythonのnonlocalキーワードは、ネストした関数(関数の中に定義された関数)におけるスコープの制御に使用されます。

具体的には、ネストした関数の内部から、その外側の関数のローカル変数にアクセスするために使用します。

def outer_func():
    x = 10  # 外側の関数のローカル変数

    def inner_func():
        nonlocal x
        x = 20  # 外側の関数のローカル変数を変更

    inner_func()
    print(x)  # 外側の関数のローカル変数を表示

outer_func()

このコードでは、outer_funcという関数の中にinner_funcという別の関数を定義しています。

inner_funcの中でnonlocal xと宣言しています。

これにより、inner_funcからouter_funcのローカル変数xにアクセスし、その値を変更します。

このプログラムを実行すると、次の出力が得られます。

20

まず、outer_funcが呼び出され、次にinner_funcが呼び出されます。

inner_funcの中で、nonlocalキーワードによりouter_funcのローカル変数xの値が20に変更されます。

その後、outer_funcの中でprint(x)が実行され、変更されたローカル変数xの値20が表示されます。

この例からわかるように、nonlocalキーワードを使用することで、ネストした関数の内部から外側の関数のローカル変数にアクセスし、その値を変更することができます。

これにより、より複雑なロジックを持つ関数を作成することが可能になります。

ただし、nonlocalキーワードもglobalキーワード同様、使用する際には注意が必要です。

なるべくシンプルなコードを書くことで、他の人が読んだ時に理解しやすくすることが重要です。

nonlocalキーワードを適切に使い、より読みやすいコードを書くことを心掛けましょう。

まとめ

Pythonのスコープは、変数の有効範囲を制御するための重要な概念です。

本記事では、Pythonのスコープに関する一般的な注意点とその対処法、スコープのカスタマイズ方法について解説しました。

具体的には、グローバル変数とローカル変数の違い、globalキーワードとnonlocalキーワードの使い方について説明しました。

これらの知識は、Pythonでのプログラミングをよりスムーズに、そしてより効率的に行うための基礎となります。

しかし、これらのキーワードは適切に使用しなければ、コードが複雑になり、保守性が低下する可能性があります。

そのため、これらのキーワードを使用する際には、常にコードのシンプルさと読みやすさを意識することが重要です。

Pythonのスコープについて混乱していた方々が、この記事を通じて、スコープの理解を深め、より実用的なコードの作成につなげていければと思います。

Pythonでのプログラミングの力を、ぜひ10倍に高めてください。