0. はじめに
本項は以下について述べます。
- Pythonにおける動的な変数定義
- 動的に定義した変数のreturnの取り方
念のため当方環境。
>>> python --version
Python 3.7.3
[前提知識]
組み込み関数に eval
exec
があります。
eval
exec
はいずれも第一引数に文字列、第二引数にグローバル名前空間、第三引数にローカル名前空間をそれぞれとります。
文字列をわたすと、eval
はそれを式として評価、exec
は文として実行してくれます。
eval('type("viscera")')
# >> str
exec('x = "squirm"; print(x)')
# >> squirm
基本的なことは以下を参照。
1. 動的な変数定義
本節では、任意の変数に格納されている文字列を変数名として新たに変数を定義する、動的な変数定義のやり方について述べます。
挙動についての留意点を列挙します。
まずはグローバル変数を定義する場合です。
この場合は思考停止で問題ありません。
def sample_a():
exec('a=3', globals())
print(a)
sample_a()
print(a)
# >> 3
# >> 3
値の変更も問題なくできます。
b = 1
def sample_b():
exec('b=3', globals())
print(b)
sample_b()
print(b)
# >> 3
# >> 3
が、ローカル変数を定義する場合には注意が必要です。
def sample_c():
exec('c=3', {}, locals())
print(c)
sample_c()
# >> NameError: name 'c' is not defined
第三引数にlocals()をわたしても、関数 sample_c
内のローカル変数を定義することはできません。従って以下のように閉じた名前空間を新たに定義する必要があります。
def sample_d():
D = {} # インスタントな名前空間
exec('d=3', {}, D)
exec('print(d)', {}, D)
sample_d()
# >> 3
この挙動の詳細については次記事を参照。
Pythonのlocals() - Qiita @amedama
以上を踏まえ、動的な変数定義が以下のようにして可能です。
def sample_e():
e = "edifice"
E = {}
exec(e + '= "a building, especially a large one"', {}, E)
exec('print(edifice)', {}, E)
sample_e()
# >> a building, especially a large one
2. return の取り方
動的な変数を定義する場合、コーディング時に変数名は未知です。
よってこの変数から返り値を取るには、
- 新たに変数を定義する。
- 定義された変数の変数名を取得し、文字列として格納する。
- 2を参照し、文字列を変数名として認識させて
return
させる。
以上3工程が要求されます。
実装にあたり、return
へ eval
で変数名を与えればよさそうだと直感的にわかりますが、新たに定義された変数名を取得するには一工夫が要りそうです。
今回は、新たに変数が定義されると、辞書である名前空間に key
が追加されることを利用します。次のようにして辞書の差分をとります。
from copy import deepcopy
f = "fertile"
FG = {}
checkpointBefore = deepcopy(FG)
exec(f + '= "able to produce good crops"', {}, FG)
checkpointAfter = deepcopy(FG)
detector = list(filter(lambda x: x not in checkpointBefore.keys(), checkpointAfter.keys()))
例によって exec
文で変数を定義しますが、その前後の名前空間を checkpoint
として deepcopy
をとり、比較して新たに増えたkey値をリスト detector
に格納します。
これを用いて、return
を取ってみます。
from copy import deepcopy
def sample_fg():
f = "fertile"
g = "germ"
FG = {}
checkpointBefore = deepcopy(FG)
exec(f + '= "able to produce good crops"', {}, FG)
exec(g + '= "a very small living thing that can make you ill"', {}, FG)
checkpointAfter = deepcopy(FG)
detector = list(filter(lambda x: x not in checkpointBefore.keys(), checkpointAfter.keys()))
return eval(detector[0], {}, FG), eval(detector[1], {}, FG)
print(sample_fg())
# >> ('able to produce good crops', 'a very small living thing that can make you ill')
ここではローカルの名前空間を例にしましたが、checkpoint
に globals()
をわたすことでグローバルの名前空間についても同様に扱うことができます。