ある変数がすでに宣言されているかどうかで処理を分けるにはどうしたら良いか調べたので記事にしておきます。また、変数以外にも組み込み関数名の一覧の確認方法とか、あるオブジェクトが持ってるプロパティの取得方法とか似てる話題をまとめておきます。
コードを雑に書いてると、if文の制御の中で変数を定義することがあります。そうなるとその以降の処理でその変数が宣言されているかどうかが不明になりますね。例えば、どこからかデータの取得を試みて、成功したらpandasのDataFrameにして、dfとかそれらしい名前の変数に格納するけど、取得失敗したらdf変数が宣言されていない状態になるみたいなケースです。
ここから紹介する内容を否定する様なことを先に書いておきますが、この様な状況では、df = pd.DataFrame() みたいに空っぽのデータフレームか何か作って確実に変数が宣言されている状態にして、その後データが取れたらdfを置き換えて、以降はlen(df)が0か1以上かで処理を分けるみたいな回避策をとった方が確実にバグが少なく可読性の高いコードが書けそうです。
せっかく調べたので記事は書きますが、豆知識くらいに思って実際は別の回避策を検討するのがおすすめです。
それではやっていきましょう。結論として、Pythonの組み込み関数の中に、宣言されている変数の一覧(シンボルテーブル)を取得するメソッドがあります。グローバルレベルで結果を返してくれるものと、関数内等で使えばローカル変数だけ返してくれるもの(モジュールレベルで使うとグローバルと同じ結果)の二つがあります。
参考: 組み込み関数 — Python 3.10.11 ドキュメント の globals()とlocals()を参照。
イメージを掴むには使ってみると早いです。辞書型で結果を返してくれます。
$ python
>>> print(globals())
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>}
# 変数を一つ宣言してみる。
>>> foo = 4
# 最後に含まれている。
>>> print(globals())
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'foo': 4}
# この様にして 変数 foo がすでに宣言されていることがわかる。
>>> "foo" in globals()
True
# locals() はスコープ内のローカル変数だけを持っている。
>>> def dummy_func():
... func_var = 1
... print(locals())
...
>>> dummy_func()
{'func_var': 1}
# スコープの外では参照できないので、globals()の結果には含まれていない。
>>> "func_var" in globals()
False
Jupyter等で長い長い作業を行ってたら、新しい変数を使うときにその時点で使ってないかの確認に使ったりとかできるかなぁとも思ったのですが、正直これを使わずにすむ様な回避策を探った方が良いと思います。僕自身、6年以上Python書いてますが、これまでglobals()やlocals()が必須な状況にはなった事ありませんし。
さて、以上で自分で宣言した変数やメソッド名の一覧(元々存在している__name__など含む)の取得方法がわかりました。
ここから先は元の主題から外れますが興味があったので調べた内容のメモです。
Pythonに限らずプログラミング言語では、あらかじめ予約語として抑えられていて使えない単語が複数あります。ifとかforとかfromとかですね。これらの名前は先ほどのglobals()の結果では出てきませんでした。
これらの予約語については、確認するための専用ライブラリが標準で用意されています。
参考: keyword — Python キーワードチェック — Python 3.11.3 ドキュメント
キーワードと、3個だけですがソフトキーワドの2種類あります。
import keyword
print(keyword.kwlist)
"""
['False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await',
'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except',
'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is',
'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try',
'while', 'with', 'yield']
"""
print(keyword.softkwlist)
"""
['_', 'case', 'match']
"""
意外と少ないですね。
今の時点で、 sum や len など普段よく使っている組み込み関数たちが登場してないので、これらの情報がどこかで取得できないかも調べました。結果わかったのは、__builtins__ ってモジュールの配下に定義されているってことです。dirメソッドで確認できます。組み込みエラーとかもここにあるんですね。
dir(__builtins__)
['ArithmeticError',
'AssertionError',
'AttributeError',
'BaseException',
'BaseExceptionGroup',
# 中略
'abs',
'aiter',
'all',
'anext',
'any',
'ascii',
'bin',
'bool',
# 中略
'enumerate',
'eval',
'exec',
'execfile',
'filter',
'float',
'format',
# 中略
'str',
'sum',
'super',
'tuple',
'type',
'vars',
'zip']
sumとかabsとかstrとかお馴染みさんたちがいましたね。
ここで使いましたが、dir()は引数で渡したオブジェクトやモジュールが持っている属性やメソッドを羅列してくれる組み込み関数です。これもメソッド探しで使うことがあります。
参考: dir([object])
__builtins__ は省略してもメソッドにアクセスできるので通常は使うことはないし、これを使わなきゃいけない様な状況も作るべきでは無いと思いますが、無理やり活用するとしたら組み込み変数名を上書きしちゃったときでも元の機能が呼び出せます。
# 元々は足し算
sum([1, 2, 3])
# 6
# 予約後と違って組み込み変数は上書き出てきてしまう。
sum = sum([1, 2, 3])
# これでsumの中身が数値6になったので、ただのsumは関数として使えなくなった。
print(sum)
# 6
# __builtins__.sum は元通り足し算として機能する
__builtins__.sum([1, 2, 3])
# 6
まぁ、常識的に考えてこんな使い方をしなくてすむ様にコードを書くべきだと思いますね。和の変数名をsumにしたり、最大値の変数名をmaxにしたりして組み込み関数を上書きしてしまった経験は初心者時代にありますが。