Python では input()
や len()
など、様々な組み込み関数を用意している。そして、これらと同じ名前で変数や関数を後から定義すると、それらを「上書き」できる。つまり、例えば input
という名前の変数にファイルから読み出したデータを格納するようなコードを書くと、以後 input
という名前は組み込み関数ではなく、読み出されたデータを指すようになるわけだ。
さて、言語仕様として許可されているコレは、お作法的には良いのか悪いのか。個人的には否定派だったのだけれど、「気持ち悪い」という主観以外に理由を挙げられずにいた。ところが先日、ついに否定の根拠になるような嫌な思いをする機会に恵まれた(?)ので、今日はそれについて書いておこうと思う。
体験例
いつもは結論を先に書くのだけれど、今回は趣向を変えて例から始めてみたい。
まず、次のようなプログラムを考えたい。標準入力から複数の数値を読み出して、行ごとに何個の数値が書かれているかを図示し、また和の表示も行っている。ただし行ごとの和を、組み込み関数 sum()
と同じ sum
という名前の変数に格納している。
import sys for i, line in enumerate(sys.stdin): numbers = [int(n) for n in line.split()] bar = "" sum = 0 for n in numbers: bar += "*" sum += n print(f"{i + 1:3d} {bar:20s} {sum:3d}")
実行例は次のような感じ。
$ cat numbers.txt 50 95 28 60 71 57 41 47 55 92 42 50 76 94 17 19 41 $ python counter.py < numbers.txt 1 *** 173 2 ********* 515 3 ***** 247
さて、ここで「和の計算については組み込み関数の sum()
に numbers
を渡した方が意図が分かりやすいし、bar
の文字列も "*" * 個数
で作れるからループは不要だな」などと思い、次のようにリファクタリングしたとしよう。
import sys for i, line in enumerate(sys.stdin): numbers = [int(n) for n in line.split()] bar = "*" * len(numbers) # NEW! total = sum(numbers) # NEW! print(f"{i + 1:3d} {bar:20s} {sum}")
組み込み関数の sum()
を使う都合上、和を格納していた sum
という変数は total
という名前に変えた。このプログラムには flake8 も mypy も特に警告を出さないし、実行してもエラーは出ない。実行すると次のような結果になる:
$ python counter.py < numbers.txt 1 *** <built-in function sum> 2 ********* <built-in function sum> 3 ***** <built-in function sum>
…そう、エラーは出ない。どう考えてもダメな実行結果なのに、エラーも出さずに正常終了してしまう。
組み込み関数と同名の変数を使うべきでない理由
組み込み関数と同じ名前の変数を使うと、後日リファクタリングでその変数名を変えるときに以下 2 つ問題が出てくるので、避けた方が良いと思う。
- リネーム漏れを見つけにくい
- リネーム漏れしたときに発生するエラーが分かりにくいか、エラーにならない
1 つ目は、変数のリネーム漏れが発生していてもツールで検出できずミスに気付きにくい、という問題。なにせ組み込み関数と同じ名前を使っていたのだから、リネーム漏れした個所は「組み込み関数を使った有効なコード」になってしまう。これに対して、もし元の変数名を sum_
のような組み込み関数とは違う名前にしてあったなら、たとえば flake8
は未定義変数だと警告を出してくれるし、走らせればそこでクラッシュするし、問題に気付きやすかったはず。
2 つ目は、(a)リネーム漏れが原因で起こるエラーのメッセージが分かりにくかったり、(b)エラーが起こらず動いてしまいトラブルシュートに苦労する、という問題。もし先のプログラム例の最後が {sum}
ではなく {sum:3d}
であった場合、TypeError: unsupported format string passed to builtin_function_or_method.__format__
というエラーメッセージが表示されるだろう。組み込み関数を 3 桁の十進数としてフォーマットしようとして「関数は十進数として表現できないよ」と怒られたわけだけれど、正直、何を間違えたのか分かりにくいと思う。また、実例で説明した先のプログラムでは「正しく動いていないのに動いているように見える」という最悪のバグが発生している状況で、誰にとっても嬉しくないと思う。
ということで、組み込み関数と同じ名前の変数の使用は避けた方が良いと思う。
以上、いつか誰かの参考になれば幸い。