Python 名前空間とスコープ グローバル変数 ローカル変数

Python学習【365日チャレンジ!】63日目のマスターU(@Udemy11)です。

LINEのスタンプが自分で簡単に作れることを知って、iPhoneのアプリでいろいろと試しているのですが、なかなか時間がかかってしまいます。

スタンプだけじゃなくて、絵文字も作れるのですが、絵文字はスタンプと違って、iPhoneのアプリ【LINE CREATORS STUDIO】では作れないので、LINE Creators Marketから操作する必要があります。

作ってはみたものの、審査はきちんとされているみたいなので、通るかどうかは微妙なところですが、できるのならちょっとLINEスタンプづくりにハマってしまいそうです。

それでは、今日のPythonを学習していきましょう!

昨日の復習

昨日は、ジェネレーター内包表記を学習しました。

def g():
    for i in range(10):
        yield(i)

g = g()
print(next(g))
print(next(g))

#上記のコードが次のように書ける
g = (i for i in range(10))
print(next(g))
print(next(g))

出力結果

0
1

ジェネレーターの内包表記は、内包表記を使用しない場合と比べるとかなり短く書けるイメージですね。

ただ、Parenthesesの丸括弧なので、タプルと間違わないようにしておきましょう。
タプルに値を入れたい場合は、()の前にtupleを記述する必要がありました。

g = tuple(i for i in range(10))
print(g)

出力結果

(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)

それでは、名前空間とスコープを学習していきましょう!

名前空間とスコープとは

名前空間は、関数や変数が所属している場所のことで、スコープはその場所から見える範囲(有効範囲)を示す言葉です。
この2つは、どうやら微妙に違うものなのですが、まー同じものと理解しておいても問題はないでしょう。

例えば、現実世界では、1年A組の鈴木花子さんも、3年A組の鈴木丸子さんも○○学校の鈴木さんには間違いありませんので、

○○学校の鈴木さんがね〜

という話を聞くと、受け取り手によって、1年A組の鈴木花子さんのことだと思う人と、3年A組の鈴木丸子さんのことだと思う人がいますが、人の会話では、別にどちらでも問題はありません。

同じ話をしていても、人によって全く受け取り方が違うので、人それぞれ解釈が違うわけです。

しかし、Pythonのようなプログラムでは、それがどちらの鈴木さんなのかわかっていないと処理をすすめることができずにエラーを返してしまいます。

例えば、変数を定義して関数の出力をする場合、関数の中と外では名前空間が違いますし、名前空間からのスコープ(有効範囲)が違います。

参照できる範囲が決め手

実際にコードでみていきましょう。

gadget = 'Mac'

def f():
    print(gadget)

f()

出力結果

Mac

関数の外で定義した変数が関数の中で使われていますが、関数の外のスコープは関数の内側を含むので、関数の外で定義した関数を利用することができます。

一方で、関数の中のスコープは、関数内だけになるので、関数内で定義したものは、関数の外で利用することはできません。

gadget = 'Mac'

def f():
    print(gadget)
    tv = 'sony'

f()
print(tv)

このコードの場合は、関数fMacと出力されたあと、次のようなエラーが起こります。

NameError: name ‘tv’ is not defined

これは、関数の内側(Global)のスコープが関数内なので、関数の外側ではtvが定義されていないと判断しているからです。

例えば、変数を同じgadgetとして考えてみるとよく分かるのですが、関数の中で、変数gadgetを出力したあと、変数gadgetを他の値に変更して、変更した値を出力しようとするとエラーが起こります。

gadget = 'Mac'

def f():
    print(gadget)
    gadget = 'iPhone'
    print('Next:', gadget)

f()

わたしなら、普通にMacが出力されたあと、変数gadgetiPhoneに変更されて、Next:iPhoneと出力されるような気がするのですが、このコードの場合、下記のエラーが返されます。

UnboundLocalError: local variable ‘gadget’ referenced before assignment

変数gadgetが定義される前に出力しようとしてますよというエラーですが、私ならここでもまだ、「gadgetは前で定義しているのにな〜」なんて思っちゃいます。

関数の外で定義されているgadgetと同じ名前の変数が関数の中に定義されているので、関数内では、中で定義されたgadgetが優先されます。

関数の中のgadgetと外のgadgetは別物として認識されるので、関数の中では、定義されていないものを出力しようとして起こるエラーなので、Pythonさんの仰せのとおり、4行目のgadget = 'iPhone'を3行目に持ってくればエラーは起こりません。

ただ、この場合は、出力される値が変わってきます。

gadget = 'Mac'

def f():
    gadget = 'iPhone'
    print(gadget)
    print('Next:', gadget)

f()

出力結果

iPhone
Next: iPhone

関数の中で定義された変数gadgetiPhoneから変更されていないので、出力は2回ともiPhoneになっています。

では、関数の外側の変数gadgetはどうなっているのかというと、最初に定義されたMacが入ったままなので、外側(インデントを上げて)で出力するとMacが出力されます。

gadget = 'Mac'

def f():
    gadget = 'iPhone'
    print(gadget)

f()
print(gadget)

出力結果

iPhone
Mac

関数外のスコープは関数内も含むけど、同じ名前があれば中で定義されたものが優先されるということです。

GlobalとLocal

中と外という表現をしてきましたが、Pythonでは、グローバル(Global)とローカル(Local)という言葉が使われています。

Pythonは、インデントを揃えることで名前空間を把握していますが、インデントなしで記載されているエリアがグローバルで、インデントされているエリアがローカルです。

これまで説明したことをこれらの言葉を使って解説すると、

グローバル変数のスコープはローカルを含んで、ローカル変数のスコープはローカル内で有効。ただし、ローカルエリアのローカル変数にグローバル変数と同じ名前が使われている場合は、ローカル変数が優先される。

という感じでしょうか?

ちなみに、グローバルとローカルで同じ名前の変数を使っている場合でも、次の方法を使えばローカル内の関数でグローバル変数を利用することが可能です。

gadget = 'iPad'

def f():
    global gadget
    gadget = 'Macmini'
    print(gadget)

print(gadget)

f()

出力結果

iPad
Macmini

global文でグローバル変数を指定することで、ローカルエリア内でもグローバル変数を扱うことができます。

上記の例では、ローカルエリア内でグローバル変数gadgetMacminiに変更したあと出力する関数を定義し、関数fを実行する前にグローバル変数gadgetを出力し、最後に関数fを実行しているので、出力がiPadMacminiになっています。

変数の参照

グローバルエリアとローカルエリアで宣言されている変数を参照したいときは、globals()メソッドやlocals()メソッドを使います。

gadget = 'Apple Watch'

def f():
    gadget = 'iMac'
    print(gadget)
    print(locals())

f()
print(globals())

出力結果

iMac
{'gadget': 'iMac'}
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x104e65860>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/Users/bigmacpro/PycharmProjects/python_programming/lesson.py', '__cached__': None, 'gadget': 'Apple Watch', 'f': <function f at 0x104de6400>}

locals()メソッドとglobals()メソッドを使えば、それぞれのエリアで宣言されている変数が辞書型で参照することができます。

ローカル変数は、一つだけですが、グローバルには、Python自体が宣言している変数がたくさんあるということですね。__name____main__と定義されているものから始まって、定義した関数もきちんと記載されています。

理解に時間がかかる

かなりプログラミングっぱい感じの内容にになってきたので、内容の理解にかなり時間がかかっています。

年齢のせいで、頭が固くなってしまって、理解度が低くなっていることもあり、何度も何度も講義を見直したり、Webサイトで検索したりしながら理解していますが、なかなか腑に落ちないんです。

とはいえ、きちんと理解していないと、どんどんわからないことが出てくるので、時間をかけてでも理解するように努力しています。

腑に落ちたときの快感がたまらないので、しっかりと理解できるように突き詰めていこうと思います。

ではでは、明日もGood Python!