この記事では、Pythonのグローバル変数と非ローカル変数について、そしてコードを書くときに問題を回避するためにそれらをどのように使用するかについて見ていきます。
まず、変数のスコープについて簡単に説明した後、グローバル変数と非ローカル変数を関数で使用する方法と理由を説明します。
Pythonのスコープ
Pythonを始める前に、まずスコープについて説明します。スコープとは、変数が定義されているコンテキストと、その変数がどのようにアクセスまたは変更されるか、より具体的には、どこからアクセスできるかを意味します。
ということです。
プログラミングでは、人生と同じように、コンテキストが重要です。
今、Pythonを参照することで、私がプログラミング言語を参照していることが文脈から推測されます。しかし、別の文脈では、Pythonは蛇やお笑いグループへの言及かもしれません。
グローバルスコープとローカルスコープは、プログラムが参照する変数の文脈を理解する方法です。
原則として、関数やクラスの中で定義された変数(インスタンス変数)はデフォルトでローカル、関数やクラスの外で定義された変数はデフォルトでグローバルになります。
Pythonのローカル変数
さて、それでは実際に使ってみましょう。まず、関数を定義して、その関数の中にローカル変数を定義します。この関数では、変数 fruit
をリストとして初期化し、表示します。
def shopping_list():
fruit = ['apple', 'banana']
print(fruit)
shopping_list()
そして、予想通り、これは魅力的に動作します。
['apple', 'banana']
しかし、printステートメントを関数の外に出すとどうなるでしょうか?
def shopping_list():
fruit = ['apple', 'banana']
shopping_list()
print(fruit)
エラーが発生します。
Traceback (most recent call last):
File "<string", line 5, in <module
NameError: name 'fruit' is not defined
特に、NameError
です。果物はローカルに定義されたので、そのコンテキストに限定されたままです。
プログラムがグローバルに(関数の外側で)変数を理解するためには、変数をグローバルに定義する必要があります。
Pythonのグローバル変数
変数を関数内で初期化するのではなく、関数の外に出してそこで初期化するとどうでしょうか。
この場合、関数の外側でそれを参照することができ、すべてがうまくいきます。
しかし、もし shopping_list
内で fruit 変数を再定義しようとすると、その変更は元のグローバル変数には反映されず、代わりにローカルに分離されます。
fruit = ['apple', 'banana']
def shopping_list():
fruit = ['apple', 'banana', 'grapes']
shopping_list()
print(fruit)
出力されます。
出力:“`
[‘apple’, ‘banana’]
これは `shopping_list()` 関数の中で変更した `fruit` が新しいローカル変数であるためです。この変数は、新しいローカル変数です。これは事実上、完全に冗長なコードです。print()` 文は、そのスコープ内にあるグローバル変数の値を表示します。
#### グローバルキーワード
もし、新しいローカル変数を作る代わりに、グローバル変数に変更を反映させたい場合は、 `global` キーワードを追加するだけでよいのです。これにより、変数 `fruit` が実際にグローバル変数であることを伝えることができます。
fruit = [‘pineapple’, ‘grapes’]
def shopping_list():
global fruit
fruit = [‘pineapple’, ‘grapes’, ‘apple’, ‘banana’]
shopping_list()
print(fruit)
そして案の定、グローバル変数は新しい値で変更され、 `print(fruit)` を呼び出すと新しい値が表示されます。
[‘pineapple’, ‘grapes’, ‘apple’, ‘banana’]
このように、fruit 変数のコンテキストをグローバル変数として定義することで、関数内で行った変更が引き継がれ、心ゆくまで再定義や変更を行うことができるのです。
また、関数の中でグローバル変数を定義して、他のどこからでも参照・アクセスできるようにすることもできます。
def shopping_list():
global fruit
fruit = [‘pineapple’, ‘grapes’, ‘apple’, ‘banana’]
shopping_list()
print(fruit)
これは、次のように出力されます。
[‘pineapple’, ‘grapes’, ‘apple’, ‘banana’]
ある関数内でグローバル変数を宣言し、別の関数でグローバル変数として指定せずにアクセスすることもできます。
def shopping_list():
global fruit
fruit = [‘pineapple’, ‘grapes’, ‘apple’, ‘banana’]
def print_list():
print(fruit)
shopping_list()
print(fruit)
print_list()
この結果は
[‘pineapple’, ‘grapes’, ‘apple’, ‘banana’]
[‘pineapple’, ‘grapes’, ‘apple’, ‘banana’]
#### グローバル変数使用時の注意点
グローバル変数をローカルに変更できるのは便利なツールですが、それなりに注意深く扱わなければなりません。過度な書き換えやスコープのオーバーライドは、バグや予期せぬ動作で終わる災いのもとです。
変数の操作は必要な場面でのみ行い、それ以外は放置することが常に重要であり、これがカプセル化の原則の背後にある主な原動力です。
グローバル変数があなたのコードでどのように役立つかを説明する前に、起こりうる問題の例を簡単に見てみましょう。
fruit = [‘pineapple’, ‘grapes’, ‘apple’, ‘banana’]
def first_item():
global fruit
fruit = fruit[0]
def iterate():
global fruit
for entry in fruit:
print(entry)
iterate()
print(fruit)
first_item()
print(fruit)
上のコードを実行すると、次のような出力が得られます。
pineapple
grapes
apple
banana
[‘pineapple’, ‘grapes’, ‘apple’, ‘banana’]
pineapple
この例では、`first_item()` と `iterate()` の両方の関数でこの変数を参照しています。まず `iterate()` を呼び出し、次に `first_item()` を呼び出せば、すべてうまくいくように見えますが、この順序を逆にしたり、あるいは `first_item()` を呼び出そうとすると、次のようになります。
この順序を逆にしたり、その後に反復処理を行おうとすると、大きな問題にぶつかります。
first_item()
print(fruit)
iterate()
print(fruit)
これは今度は出力されます。
pineapple
p
i
n
e
a
p
p
l
e
pineapple
つまり、`fruit` はイテレートされる文字列になっています。さらに悪いことに、このバグはおそらく手遅れになるまで現れない。最初のコードは一見問題なく実行できました。
さて、この問題は意図的に明らかなものです。グローバル変数を直接いじってしまったのです。なんと、変わってしまったのです。しかし、より複雑な構造では、誤ってグローバル変数の変更を一歩進めてしまい、予期せぬ結果を招いてしまうかもしれません。
### 非局所的なキーワード
注意しなければならないからと言って、グローバル変数が信じられないほど便利でないわけではありません。グローバル変数は、カウンターのように return 文で変数を指定せずに更新したい場合に便利です。また、関数を入れ子にした場合にも非常に便利です。
Python 3+ を使っている人は、 `nonlocal` を使うことができます。このキーワードは `global` と非常に似た働きをしますが、主にメソッドの中でネストされたときに効果を発揮します。このキーワードは `global` と非常に似た働きをしますが、主にメソッドのネスト時に効果を発揮します。 `nonlocal` は本質的にグローバルとローカルの中間的なスコープを形成します。
ほとんどの例で買い物リストと果物を使っているので、購入したものを合計するチェックアウト機能を考えることができます。
def shopping_bill(promo=False):
items_prices = [10, 5, 20, 2, 8]
pct_off = 0
def half_off():
nonlocal pct_off
pct_off = .50
if promo:
half_off()
total = sum(items_prices) – (sum(items_prices) * pct_off)
print(total)
shopping_bill(True)
上のコードを実行すると、次のような出力が得られます。
22.5
この方法では、グローバル変数countは外側の関数にローカルに存在し、より高いレベルでは影響しない(または存在しない)。これにより、関数に修飾語を追加する際に、ある程度の自由度が与えられる。
ショッピングビルメソッドの外側で `pct_off` を表示してみれば、いつでもこれを確認することができます。
NameError: name ‘pct_off’ is not defined
もし、 `nonlocal` キーワードの代わりに `global` キーワードを使用していたら、 `pct_off` をプリントすると、次のようになります。
0.5
“`
結論
結局のところ、グローバルキーワード(および非ローカルキーワード)はツールであり、適切に使用すればあなたのコードに多くの可能性を開くことができます。私自身、この2つのキーワードを自分のコードで頻繁に使っていますし、十分に練習すれば、このキーワードがどれほど強力で便利なものであるかを理解できるようになるでしょう。
いつものように、読んでくださってありがとうございます。
</module