よくあるPythonの問題 - [11] Pythonのスコープルールに関する誤解

By JoeVu, at: 2023年1月20日12:39

Estimated Reading Time: __READING_TIME__ minutes

Common Python Problems - [11] Misunderstanding Python scope rules
Common Python Problems - [11] Misunderstanding Python scope rules

Pythonの主要な機能の1つに、プログラム内で変数がどのようにアクセスおよび変更されるかを決定するスコープルールがあります。

 

しかし、Pythonのスコープルールは多くの開発者にとって混乱の源となり、エラーや予期せぬ動作につながることがあります。

 

この記事では、Pythonのスコープルールの一般的な誤解をいくつか解説し、それらを正しく使用する方法の例を示します。

 

1. Python スコープルールの概要

 

Pythonには、名前空間とも呼ばれる4種類のスコープがあります。

 

  • 組み込み名前空間:Pythonで使用可能なすべての組み込み関数と定数を含みます。
     
  • グローバル名前空間:関数やクラスの外部で定義され、プログラム内のどこからでもアクセスできる変数を含みます。
     
  • クロージャ名前空間:外側の関数で定義され、内側の関数からアクセスできる変数を含みます。
     
  • ローカル名前空間:関数内で定義され、その関数内でのみアクセスできる変数を含みます。

 

変数にアクセスまたは変更を加える場合、Pythonは特定の順序でこれらの名前空間を検索して変数の値を決定します。

 

Pythonは名前空間のルックアップにLEGBルール(Local、Enclosing、Global、Built-in)に従います。変数が関数内で参照されると、Pythonは最初にローカル名前空間、次にクロージャ名前空間、次にグローバル名前空間、最後に組み込み名前空間を検索します。これらの名前空間のいずれにも変数が見つからない場合、NameErrorが発生します。

 

2. Pythonスコープルールの誤解

 

2.1 誤解1:グローバル変数は関数内から変更できる

 

Pythonのスコープルールの一般的な誤解の1つは、グローバル変数を関数内から変更できることです。グローバル変数には関数内からアクセスできますが、直接変更することはできません。代わりに、グローバルキーワードを使用して、変数をグローバル変数として扱い、それに応じて変更する必要があることを示す必要があります。

 

たとえば、次のコードを考えてみてください。

 

x = 0

def increment():
    x += 1
    print(x)

increment()

 

このコードは、UnboundLocalError: local variable 'x' referenced before assignmentというエラーを発生させます。このエラーの理由は、Pythonがxを関数内で変更されているためローカル変数であるとみなすためです。これを修正するには、グローバルキーワードを追加して、xがグローバル変数であることを示します。

 

x = 0

def increment():
    global x
    x += 1
    print(x)

increment()

 

これで、xがグローバル変数として適切に扱われ、インクリメントされるため、出力は1になります。

 

2.2 誤解2:ループ内の変数はローカル変数である

 

Pythonのスコープルールのもう1つの一般的な誤解は、ループ内で定義された変数はローカル変数であるということです。しかし、これは事実ではありません。ループ内で定義された変数は実際にはクロージャ名前空間にあると見なされ、ネストされた関数内からアクセスできます。

 

たとえば、次のコードを考えてみてください。

 

for i in range(5):
    def print_i():
        print(i)
    print_i()

 

このコードは、iがクロージャ名前空間にあると見なされ、ネストされた関数print_i()内からアクセスできるため、0 1 2 3 4を出力します。

 

2.3 誤解3:ある関数で定義された変数は別の関数からアクセスできる

 

Pythonのスコープルールの3番目の一般的な誤解は、ある関数で定義された変数は別の関数からアクセスできるということです。しかし、これは事実ではありません。各関数には独自のローカル名前空間があり、ある関数で定義された変数は、引数として渡されない限り、別の関数からアクセスできません。

 

たとえば、次のコードを考えてみてください。

 

def outer():
    x = 1
    def inner():
        print(x)
    inner()

outer()


このコードは、inner()がxを含むクロージャ名前空間にアクセスできるため、1を正しく出力します。しかし、新しい関数another_inner()を定義して、そこからxにアクセスしようとすると、NameErrorが発生します。

 

def outer():
    x = 1
    def inner():
        print(x)
    def another_inner():
        print(x)
    inner()
    another_inner()

outer()


このコードは、NameError: name 'x' is not definedというエラーを発生させます。これは、another_inner()がinner()のローカル名前空間にアクセスできないためです。

これを修正するには、xを引数としてanother_inner()に渡します。

 

def outer():
    x = 1
    def inner():
        print(x)
    def another_inner(x):
        print(x)
    inner()
    another_inner(x)

outer()


これで、inner()とanother_inner()の両方から出力されるため、1が2回正しく出力されます。

 

3. まとめ

 

Pythonのスコープルールは多くの開発者にとって混乱の源となる可能性がありますが、その仕組みをしっかりと理解することで、一般的な誤解を避け、より信頼性の高いコードを作成できます。LEGBルールを覚えて、グローバルキーワードと関数引数を適切に使用することで、プログラム内で変数が正しくアクセスおよび変更されるようにすることができます。

 

Tag list:
- Python
- Scope
- Scope Rules
- Global Variables
- Global Keywords
- Local Namespace
- Global Namespace
- NameError
- UnboundLocalError

Related

Python

Read more
Python

Read more
Python

Read more

Subscribe

Subscribe to our newsletter and never miss out lastest news.