Pythonでexec文字列の中にプログラムを書くとNameErrorになる
あけましておめでとうございます。本年もよろしくお願いします。
Python3で以下のようなコードを実行したところ、エラーが発生しました。
実験の趣旨はexecにプログラムを文字列で渡すことです。
#! /usr/bin/env python3
# hash_exec_test.py
def exec_test(s):
exec(s)
exec_test ('''
book = {"Apple":[120,15], "Orange":[95,32], "Banana":[60,71]}
print("↓↓One-Values↓↓")
print(book["Apple"])
print()
keys = [x for x in list(book.keys())]
print("↓↓All-Keys↓↓")
print(keys)
print()
values = [book[x] for x in list(book.keys())]
print("↓↓All-Values↓↓")
print(values)
''')
実行結果は以下のとおりです。
$ ./hash_exec_test.py
↓↓One-Values↓↓
[120, 15]
↓↓All-Keys↓↓
['Apple', 'Banana', 'Orange']
Traceback (most recent call last):
File "./hash_exec_test.py", line 19, in <module>
''')
File "./hash_exec_test.py", line 5, in exec_test
exec(s)
File "<string>", line 10, in <module>
File "<string>", line 10, in <listcomp>
NameError: name 'book' is not defined
bookという名前が定義されていない、と言われています。
しかし、上ではbook["Apple"]という検索が成功していますし、その後でキーを取り出すことはできているのに、最後に値を取り出そうとすると成功しない理由が分かりません。
ちなみに、関数exec_testを介さずに、execに文字列を渡してみます。
#! /usr/bin/env python3
# hash_exec_test.py
exec ('''
book = {"Apple":[120,15], "Orange":[95,32], "Banana":[60,71]}
print("↓↓One-Values↓↓")
print(book["Apple"])
print()
keys = [x for x in list(book.keys())]
print("↓↓All-Keys↓↓")
print(keys)
print()
values = [book[x] for x in list(book.keys())]
print("↓↓All-Values↓↓")
print(values)
''')
これだとうまくいきます。
$ ./hash_exec_test.py
↓↓One-Values↓↓
[120, 15]
↓↓All-Keys↓↓
['Banana', 'Orange', 'Apple']
↓↓All-Values↓↓
[[60, 71], [95, 32], [120, 15]]
なお、ディクショナリ型bookをグローバル変数にしてみます。
#! /usr/bin/env python3
# hash_exec_test.py
def exec_test(s):
exec(s)
book = {"Apple":[120,15], "Orange":[95,32], "Banana":[60,71]}
exec_test ('''
print("↓↓One-Values↓↓")
print(book["Apple"])
print()
keys = [x for x in list(book.keys())]
print("↓↓All-Keys↓↓")
print(keys)
print()
values = [book[x] for x in list(book.keys())]
print("↓↓All-Values↓↓")
print(values)
''')
これでも、うまくいきます。
$ ./hash_exec_test.py
↓↓One-Values↓↓
[120, 15]
↓↓All-Keys↓↓
['Orange', 'Apple', 'Banana']
↓↓All-Values↓↓
[[95, 32], [120, 15], [60, 71]]
なぜ一番上のケースで、値のリストを得ようとするところだけNameErrorになるのでしょうか。
ご教示願います。
ちなみに環境は以下のとおりです。
$ uname -a
Linux DESKTOP-AHPUUO5 4.4.0-43-Microsoft #1-Microsoft Wed Dec 31 14:42:53 PST 2014 x86_64 x86_64 x86_64 GNU/Linux
$ python3 -V
Python 3.5.2
(2017-01-02 11:51 追記)
ローカル変数の定義状況を示すためにprint(locals())を加えてみました。
#! /usr/bin/env python3
# hash_exec_test.py
def exec_test(s):
exec(s)
exec_test ('''
book = {"Apple":[120,15], "Orange":[95,32], "Banana":[60,71]}
print("locals")
print(locals())
print()
print("One-Values:")
print(book["Apple"])
print()
keys = [x for x in list(book.keys())]
print("All-Keys")
print(keys)
print()
print("locals in the list comprehension")
values = [locals() for x in list(book.keys())]
print(values)
print("All-Values")
values = [book[x] for x in list(book.keys())]
print("All-Values")
print(values)
''')
実行結果は以下のとおりです。
$ ./hash_exec_test.py
locals
{'s': '\nbook = {"Apple":[120,15], "Orange":[95,32], "Banana":[60,71]}\nprint("locals")\nprint(locals())\nprint()\nprint("One-Values:")\nprint(book["Apple"])\nprint()\nkeys = [x for x in list(book.keys())]\nprint("All-Keys")\nprint(keys)\nprint()\nprint("locals in the list comprehension")\nvalues = [locals() for x in list(book.keys())]\nprint(values)\nprint("All-Values")\nvalues = [book[x] for x in list(book.keys())]\nprint("All-Values")\nprint(values)\n', 'book': {'Banana': [60, 71], 'Orange': [95, 32], 'Apple': [120, 15]}}
One-Values:
[120, 15]
All-Keys
['Banana', 'Orange', 'Apple']
locals in the list comprehension
[{'x': 'Apple', '.0': <list_iterator object at 0x7f871a502470>}, {'x': 'Apple', '.0': <list_iterator object at 0x7f871a502470>}, {'x': 'Apple', '.0': <list_iterator object at 0x7f871a502470>}]
All-Values
Traceback (most recent call last):
File "./hash_exec_test.py", line 25, in <module>
''')
File "./hash_exec_test.py", line 4, in exec_test
exec(s)
File "<string>", line 17, in <module>
File "<string>", line 17, in <listcomp>
NameError: name 'book' is not defined
リスト内包表記の中ではローカル変数bookが定義されていません。
これが原因なのでしょう。
ではなぜ
keys = [x for x in list(book.keys())]
は正しく動いたかというと、book.keys()は<list_iterator object at 0x7f871a502470>を介して(?)bookにアクセスできていたということでしょうか。