CPythonの関数が参照している名前(ローカル変数と自由変数を除く)は、その関数のコードオブジェクトのco_names
に保存されていることは以前書きました。co_names
は、グローバル変数名だけでなく、例えば次の例ではy
も含んでいます。
def foo():
return x.y
foo.__code__.co_names # => ('x', 'y')
ではグローバル変数の名前だけを取り出したい場合はどうするかについてです。
結論から言うと、dis
標準ライブラリで、その関数をディスアセンブリし、LOAD_GLOABL
インストラクションの引数を取り出す、という方法があるようです。
import dis
def global_names(func):
insts = list(dis.get_instructions(func.__code__))
names = []
for inst in insts:
if inst.opname == "LOAD_GLOBAL" and inst.argval not in names:
names.append(inst.argval)
return tuple(names)
ただこの方法だと、ネストした関数内で参照されているグローバル変数名は取得できないようです。
def foo():
def bar():
return x
return bar()
global_names(foo) # => ()
2021/7/11 追記
ジェネレータ表記やネストした関数で参照されているグローバル変数名も取得するには、関数のコードオブジェクトのco_consts
の要素でコードオブジェクトである要素を再びディスアセンブルし、LOAD_GLOBAL
のインストラクションの引数を取り出せばよいようです。
import dis
from types import CodeType
def global_names(func):
return tuple(_extract_globals(func.__code__))
def _extract_globals(codeobj):
insts = list(dis.get_instructions(codeobj))
names = []
for inst in insts:
if inst.opname == "LOAD_GLOBAL" and inst.argval not in names:
names.append(inst.argval)
# Extract globals in generators and nested functions
for co in codeobj.co_consts:
if isinstance(co, CodeType):
names.extend(_extract_globals(co))
return names