Pythonの変数スコープの話

# Pythonは変数のスコープが曖昧な事を忘れない
たまに忘れてハマるので、覚書ついでに共有です。

## グローバル変数とローカル変数
Pythonは関数内で変数を呼び出した時、グローバルですでに定義されていた場合はグローバル変数が使用されます。

```python
global_var = "Global Varibale"
def get_global():
    local_var = global_var
    return local_var
print(get_global())

>>> Global Variable
```

この時、呼び出し後に関数内で代入処理を行った場合、ローカルのスコープとして扱われ例外が発生します。
（Pythonは関数内で代入が行われると、初めてローカルスコープとしてその変数を取り扱います）

```python
global_var = "Global Varibale"
def get_global():
    local_var = global_var
    global_var = "Reset Local" # Add
    return local_var
print(get_global())

>>> Traceback (most recent call last):
>>>   File "test.py", line 7, in <module>
>>>     print(get_global())
>>>   File "test.py", line 3, in get_global
>>>     local_var = global_var
>>> UnboundLocalError: local variable 'global_var' referenced before assignment
```

グローバル変数を使用していると思い、グローバル変数を上書きするつもりで代入処理を行うと例外になるためスコープ外の変数を扱う場合は注意が必要です。

これを防ぐために、グローバル変数で使用されている（もしくは使用される可能性がある変数名を指定する）場合は、関数定義直後に初期化を行うことをおすすめします。

```python
common_var = "Global Varibale"
def get_global():
    common_var = ""
    # Intermediate processing
    common_var = "Local Variable"
    return common_var

print(get_global())
print(common_var)

>>> Local Variable
>>> Global Varibale
```

また、グローバル変数を関数内で使用する場合は、 `global` で定義することで明示的にグローバル変数を使用することができます。

```python
common_var = "Global Varibale"
def get_global():
    global common_var
    # Intermediate processing
    common_var = "Local Variable"
    return common_var

print(get_global())
print(common_var)

>>> Local Variable
>>> Local Variable
```

## クラスで定義する変数
* 追記
    - 以下のグローバルスコープは `インスタンスからみてグローバル = クラス変数` という意味です。
    - 逆に、ローカルスコープは `インスタンスから見て閉じている = インスタンス変数` という意味です。
    - 上の記述と合わせるために文言を統一しました。

　
* クラスに定義した変数をインスタンス内で再度代入する。

```python
class TestClass(object):
    common_var = "Initial Value"
    def get_variable(self):
        return self.common_var
    def set_variable(self, value):
        self.common_var = value

tc1 = TestClass()
tc2 = TestClass()
tc1.set_variable("Set Variable")
print(tc2.common_var)

>>> Initial Value
```

この場合、値を代入することでスコープがインスタンス内に限定され、 `tc2` の変数は上書きされません。
(関数と同様にメソッド内で代入処理を行うと、ローカルスコープとして取り扱われます。 クラス変数として操作したい場合は、クラスを指定する必要があります。)

しかし、これがMutableな変数であり、代入処理が行われないとグローバルスコープで処理され `tc2` へも影響を及ぼします。

```python
class TestClass(object):
    common_var = []
    def get_variable(self):
        return self.common_var
    def set_variable(self, value):
        self.common_var.append(value)

tc1 = TestClass()
tc2 = TestClass()
tc1.set_variable("Set Variable")
print(tc2.common_var)

>>> ['Set Variable']
```

これらの動作は、Pythonは元々オブジェクト指向の設計ではなく、前述の話が理解できていればある程度想定はできる事象だと思います。
~~(とはいえ、普通は考えにくい動作なので忘れてるとハマります。）~~
指摘頂いたので修正します。
common_varが `クラス変数である` という前提であればなんらおかしい動作ではありませんが、前述の動作を考慮すると分かりにくい動作になっています。

しかし、メソッドのキーワード引数に対してMutableな値を初期値として渡した場合も、初期値として渡しているにもかかわらずグローバルスコープで取り扱われるという仕様があります。
（初期値として渡しているので前述の理論からはローカルスコープになりそうですが、そうはなりません）

```python
class TestClass(object):
    def __init__(self):
        self.common_var = None
    def set_variable(self, values=[]):
        values.append("Set Variable")
        self.common_var = values

tc1 = TestClass()
tc2 = TestClass()
tc1.set_variable()
tc2.set_variable()
print(tc2.common_var)

>>> ['Set Variable', 'Set Variable']
```

通常であれば、 `tc2.set_variable()` は一度しか呼ばれていないためリストの中身は一つだけであることを期待しますが、 `tc1.set_variable()` でappendしたデータも含まれてしまっています。
以上から、キーワード引数の評価はクラスが呼ばれた後一度しか行われず、その後はグローバルスコープとして取り扱われるということがわかります。
（評価されない=初期化されないので、前述の理論からグローバルスコープとして取り扱われることがわかります。）

このように、Pythonは変数のスコープが曖昧であるため、特にMutableな変数（代入が行われずに使用されることが想定される）の場合は初期化を、クラス変数を取り扱う場合はクラスを指定して操作することを意識する必要があります。