【Python3】関数内でグローバル変数を動的定義

# はじめに

動機：関数内でグローバル変数の動的な定義＆値の代入がしたい！

（こんなことホイホイとやっちゃダメですが…。）

```py
def dainyuu(val_name):
    global val
    val = 123  # ここで指定された名前の変数に代入したい

dainyuu("val")
print(val)  # 関数外で参照
```

```exec()``` によって動的な変数の定義や代入が可能ですが、グローバル変数への代入で少し手間取ったのでまとめました。

なお、検証は Python 3.7.3 です。

- 公式リファレンス：[組み込み関数#exec](https://docs.python.org/ja/3/library/functions.html#exec)

# パターン１：グローバル変数のシンプルな定義

- ```exec``` の第2引数に ```globals()``` を渡す
- 第3引数を渡さない

```exec``` に ```globals()``` のみを渡すことによって、文字列中の代入はグローバル変数への代入になります。

```py
# 特に指定がない場合、ローカル変数の定義になる
def define_variable_test1(var_name):
    exec("{0} = 123".format(var_name))
    eval("print('{0}(in) : ' + str({0}))".format(var_name))

# globals() を渡すとグローバル変数の定義になる
def define_variable_test2(var_name):
    exec("{0} = 345".format(var_name), globals())
    eval("print('{0}(in) : ' + str({0}))".format(var_name))


define_variable_test1("val1")
try:
    print("val1(out): {0}\n".format(val1))
except NameError:
    print("val1(out): not defined\n")

define_variable_test2("val2")
try:
    print("val2(out): {0}\n".format(val2))
except NameError:
    print("val2(out): not defined\n")
```

```:out
val1(in) : 123
val1(out): not defined

val2(in) : 345
val2(out): 345
```

# パターン２：グローバル変数の定義にローカル変数を使う

- ```exec``` に ```globals()``` と ```locals()``` を渡す
- 評価文中で ```global ``` 変数宣言を行う

```exec``` に ```locals()``` を渡すと文字列中の代入はローカル変数になりますが、同じ文字列中で ```global``` 宣言を行っておくことでグローバル変数への代入が行えます。

```py
# locals() も渡すとローカル変数の定義になってしまう
def define_variable_test3(var_name):
    local_val = 12345
    exec("{0} = local_val".format(var_name), globals(), locals())
    eval("print('{0}(in) : ' + str({0}))".format(var_name))

# exec 内でのグローバル変数宣言によってグローバル変数の定義になる
def define_variable_test4(var_name):
    local_val = 34567
    exec("global {0}\n{0} = local_val".format(var_name), globals(), locals())
    eval("print('{0}(in) : ' + str({0}))".format(var_name))


define_variable_test3("val3")
try:
    print("val3(out): {0}\n".format(val3))
except NameError:
    print("val3(out): not defined\n")

define_variable_test4("val4")
try:
    print("val4(out): {0}\n".format(val4))
except NameError:
    print("val4(out): not defined\n")
```

```:out
val3(in) : 12345
val3(out): not defined

val4(in) : 34567
val4(out): 34567
```

当たり前と言えば当たり前ですが、引数として ```global()``` とかを渡せるのもあって無駄に悩みました…

というか、こんな邪道を歩む行為やめようねって話です。

# パターン３：```globals()``` の返り値を直接編集する

コメントに寄せて頂いた情報です。
く、黒魔術だ…！

```py
# globals() で返る辞書を直接編集してしまうことも可能
def define_variable_test5(var_name):
    local_val = 1234567
    globals()[var_name] = local_val
    eval("print('{0}(in) : ' + str({0}))".format(var_name))

define_variable_test5("val5")
try:
    print("val5(out): {0}\n".format(val5))
except NameError:
    print("val5(out): not defined\n")
```

```:out
val5(in) : 1234567
val5(out): 1234567
```

普通に代入が成功してますが、正直言ってあまり推奨される行為ではないと思うのでご注意ください。
将来的に動作が変わる可能性もあります。

なお、```globals()``` では成功しましたが、```locals()``` では関数内での代入は失敗します。（実際のローカル変数に変更が反映されない）

[組み込み関数#locals()](https://docs.python.org/ja/3/library/functions.html#locals)
>**注釈**: この辞書の内容は変更してはいけません; 変更しても、インタプリタが使うローカル変数や自由変数の値には影響しません。 

```py
def changing_local_variable_test():
    var = 123
    print("before: {0}".format(var))
    locals()["var"] = 234
    print("after:  {0}".format(var))

changing_local_variable_test()
```

```:out
before: 123
after:  123
```