本当に誰でも読める!! Pythonソースコードの読み方講座(第9回)Python コードを俯瞰する 2:コードの領域間の「見え方」について

  • HOME
  • 本当に誰でも読める!! Pythonソースコードの読み方講座(第9回)Python コードを俯瞰する 2:コードの領域間の「見え方」について

本記事は当社が発行しているシミュレーションメールマガジンVol.9の記事です。 シミュレーションメールマガジンの詳細・購読申込はこちら のサポートページから

プログラマにもノン・プログラマにも読んでいただきたい「Pythonコードの読み方」講座の第9回です。前回に引き続き 「Python コードを俯瞰する」と題して、Python コードの全体像について解説します。とりわけ、領域同士の関係、「コードのある部分から、別の領域がどのように見えるか」について説明していきます。
コードを書く方にとっても有用な視点になると思いますのでぜひご一読ください。

領域間の関係の原則

a = "a"
b = "b"
def function(a):
    print("関数内:", a, b)
    a = "X"

print("関数呼び出し前:", a, b)
function("A")
print("関数呼び出し後:", a, b)

ひとつの関数定義とその呼出しからなるシンプルな例です。前回の呼称に沿うと、関数内ローカル領域(l.4-5)とグローバル領域(それ以外)の2種類の領域があることになります。

領域同士の関係を考えるとき、以下の原則があります:

  • 原則1.: 自分より外の領域は見えるが、自分の内側の領域は見えない
  • 原則2.: 変数の値を決めるときは自分の領域から始めて、外の領域を順番に探していく

このコードを実行すると以下のような出力が得られます:

関数呼び出し前: a b
関数内: A b
関数呼び出し後: a b

さて、この結果は次のように説明することができます:

関数を呼び出し前
この時点では a, b の値はそれぞれ文字列の "a", "b" になっています。l.5 に a = "X" という代入文が見えますが、関数内ローカル領域はこの時点ではそもそも実行されていないため関係ありません(領域と実行順序に関しては前回の内容を参照ください)。

関数内
関数 function は引数として a を受け取っています。 a, b それぞれの値は次のように求められます: * a は同じ関数内ローカル領域に引数として与えられているため、l.8 の引数の値である "A" となります * b は関数内ローカル領域に存在しないため、「原則2.」に従ってひとつ外側のグローバル領域を見にいきます。グローバル領域の b はこの時点では "b" であるから、関数内ローカル領域の b の値も "b" となります。

関数呼び出し後
関数内で a"X" に書き換えられますが、関数呼び出し後のグローバル領域 a の値は "a" のままです。これは「原則1.」より関数内ローカル領域はグローバル領域からは見えないためです。

外側の領域の変数を書き換える

次の例は先のものに l.6 を挿入したものです。このように、関数内で a だけでなく b の値も書き換えようとするとどのようになるでしょうか?

a = "a"
b = "b"
def function(a):
    print("関数内:", a, b)
    a = "X"
    b = "Y"

print("関数呼び出し前:", a, b)
function("A")
print("関数呼び出し後:", a, b)

結果は、以下のように「b が存在していない」という意味のエラーとなります:

UnboundLocalError                         Traceback (most recent call last)
 in 
      7 
      8 print("関数呼び出し前:", a, b)
----> 9 function("A")
     10 b = "B"
     11 print("関数呼び出し後:", a, b)

 in function(a)
      2 b = "b"
      3 def function(a):
----> 4     print("関数内:", a, b)
      5     a = "X"
      6     b = "Y"

UnboundLocalError: local variable 'b' referenced before assignment

この例の関数内ローカル領域では a, b ともに l.5-6 に代入文が書かれているため、ローカル領域に属する変数(=ローカル変数)として扱われます。a は引数として与えられているため print の時点で値を取得することができますが、b についてはまだ値の設定がされていないため、「未割り当てのローカル変数である」としてエラーになります。
最初の例ではグローバル領域に値を見に行くことで print できていましたが、代入文を書いたことで変数 b の扱いが変化してグローバル領域を見に行くことがなくなった、ということです。

内側の領域から外側の領域の変数を(参照するだけでなく)変更したい、という場合は global nonlocal といったキーワード(予約語)を用いることになります。
今回の場合であれば、関数の最初に

global b

のように global 文で b がローカル変数でないことを宣言することで所望の結果を得ることができます。

コード:

a = "a"
b = "b"
def function(a):
    global b
    print("関数内:", a, b)
    a = "X"
    b = "Y"

print("関数呼び出し前:", a, b)
function("A")
print("関数呼び出し後:", a, b)

出力:

関数呼び出し前: a b
関数内: A b
関数呼び出し後: a Y

いかがでしょうか。関数内でローカル変数として扱われている a は関数内の書き換えが外からは見えないのに対して、関数内でグローバル変数として扱われている b は書き換えが見えていることが分かると思います。 global, nonlocal キーワードの詳細については公式ドキュメントも参照ください。

今回は関数内ローカル領域 vs グローバル領域の2領域について、お互いがどのように見えているかを観察しました。また、内側の領域から外側の変数を書き換える方法について一部説明しました。
次回はより複雑な例を用いて領域間の関係についての理解を深めていきたいと思います。

豊岡 祥 株式会社 NTTデータ数理システム シミュレーション&マイニング部所属。
S4 Simulation System の開発のほか、機械学習・シミュレーション・数理最適化を幅広く扱い、分野を横断した問題解決に取り組んでいる。
入社後に競技プログラミングをはじめ、PGBATTLE2023にて企業の部団体3位入賞。

著書: 「XAI(説明可能なAI)──そのとき人工知能はどう考えたのか?」リックテレコム
趣味: 合唱
「数理科学の基礎知識」e-book無料ダウンロードはこちら