- ローマ数字からアラビア数字へ変換する?
- ローマ数字 => アラビア数字、を少しだけ直しましたが、まだまだのようです?
- ローマ数字からアラビア数字へ変換する。泥臭くやってみました(笑)
- ローマ数字からアラビア数字へ変換する。またですか?
- ローマ数字からアラビア数字へ変換する。またまたです!
- Pythonのreduceに注目です。同じ例題ですが…。
なかなか苦戦してる模様だ。
いや、isamさんだけ、じゃない。reduceにはみんな最初は引っかかる。
事実、「Python reduce」で検索すると、たくさんの「How to」及び「使い方入門」のページがヒットする。
それだけreduceは使い方が「分からない」と思われてる、って事だ。
僕だって最初はreduceの意味が分からなかった。
僕とreduceの初邂逅はポール・グレアムの書籍「ANSI Common Lisp」だった。
そこではこんな事が書いてあった。
2つの引数しか取れない関数をreduceで拡張出来る。
それで「ああ、reduceってのは2引数関数を可変長関数に変えれるのね」と盛大に勘違いした(笑)。いや、確かにそういう効果はあるんだけれども(笑)。
ちなみに、ポール・グレアムが挙げてた例はPythonで書くと次のようなカンジだ。
"brad's", "bad", "cat"に含まれる共通の文字は"a"だ。Pythonでは、2つの集合に対する積(あるいは交叉)を求める演算子は&で、中置記法なので被演算子は2個しか取れないが、確かにreduceはその制限を取っ払い、上の例だと3つの集合の積を計算してる。
まぁ、これを最初見た時「なるへそ」たぁ思ったけど、そこまでreduceに魅力は感じてなかったんだよな(笑)。
紫藤のページでもreduce(fold)の説明が書いてたけど、やっぱり初見じゃ「意味が分からん」だった(笑)。
そもそもSchemeの仕様にはreduce(fold)は含まれていない。そんな爪弾きにされてる関数が「何故にここで紹介される程」重要な高階関数なのか、サッパリ分からんかったんだよ。
そう、だから僕も最初reduceの存在意義がサッパリ分からんトコからスタートしたんだ。いや、むしろ皆と同じで、「reduceはワケが分からん」ってぇんで嫌ってた。そして「なるたけ使わないようにしよう」とまで思ってたんだ。
そんな中、当時ブイブイ言ってたGoogleのMapReduceの存在を知る(※1)。Google所属のハッカー達は関数型言語の事も良く知ってる。彼らは作り上げたサービスに最重要高階関数mapと共に、reduceをその名に冠したんだ。
「Googleのハッカー達はmapとreduceを関数型言語の二本柱って考えてるんだ・・・。」
僕は基本的にはミーハーなんで(笑)、Googleのハッカーがそう考えてるなら、と俄然reduceの攻略に乗り気になったわけ(笑)。
それで、何度も紹介するけど、reduceの強力さを実感するにはこの文書を読むべきだ。何度も紹介するのは僕自身何度もこの文書を読んだから、だ。そしてこの文書はそれだけ重要な事が書いてある。
それはさておき、まずは基本的なreduceの使い方をおさらいしよう(※2)。
1. reduceの基本的な使い方と注意事項
まずは一番典型的なreduceの使い方、から。
>>> reduce(lambda x, y: x + y, [1, 2, 3, 4])
10
これは典型的に良く見られる「一番簡単な」reduceの使用例だ。第二引数に与えられたリストの総和を取る、と。
>>> sum([1, 2, 3, 4])
10
そういう意味で言うと、実は上のreduceの使い方は実用的な例ではない。
しかし、これが示唆してるのは「sumはreduceで書ける」と言う事で、言い換えると、reduceの汎用性は群を抜いてる、と言う証拠だ。
そして、加算は例えば1 + 2と2 + 1は同じ結果になる。従って最も簡単なreduceの使い方なのは間違いなく、このレベルでは誰も混乱しないんだ。
しかしながら、次の例辺りから混乱する人が続出してくる。僕も最初引っかかったのがこれだ。
>>> reduce(lambda x, y: [y] + x, [1, 2, 3, 4], [])
[4, 3, 2, 1]
これは言わばreversedのreduceを使った実装例だ。
何に戸惑うのか、と言うとラムダ式、あるいはコールバック関数の引数の順序とreduce本体に置かれた操作対象の引数順序が逆になってる辺りだ。
- reduce本体のコールバック関数以降の引数 -> イテラブル、初期値、の順
- コールバック関数の引数 -> xは初期値を指し、yがイテラブルから取った値の順
これが混乱の原因だ。
先に見た通り、加算の場合はx + yだろうとy + xだろうと演算子の性質によりどっちでも良かったが、リスト操作になるとこの、「引数順序の不一致」が顔を出して戸惑う。
多くの人が「reduceを嫌う」原因がこれ、なんだ。
Lispなんかだと、この「引数順序の不一致」の原因は、実装上の理由で、要は操作対象のリストを表す引数が可変長引数である事に由来する。リストはいくつでも取れてそれら対象に操作出来るが故にこの不具合が存在するんだ。
一方、例えばOCamlやF#みたいな言語だとreduce(List.fold_rightやList.fold_left)のリストを表す引数が固定長だ。要はリストは一個しか取れないんで、実装上この不具合は存在しない。
# List.fold_left (fun x y -> y :: x) [] [1; 2; 3; 4];;
- : int list = [4; 3; 2; 1]
(* OCamlでの例。本体の引数順序とコールバック引数(ラムダ式)の引数順序が一致してるので、「どういう操作」なのか混乱はしない。 *)
じゃあPythonは?と言うと、イテラブルを取れる引数は固定長で、OCamlと同様に一個しか取れない(これは後で見るが問題にはならない)。しかしながら、初期値(第3引数)がデフォルト引数(None)になってる為、Lispと同様に引数順序がおかしな事になってるんだ(デフォルト引数は設計上、引数リストの最後尾に置かないとならない)。
多分ANSI Common Lispを真似たんだろうが、正直な事を言うと「バカな設計にしたな」とか思ってる(笑)。イテラブルを一個しか取れないけど、初期値を隠さなかったら、OCaml/F#と同様に引数順序は一致してた筈なんだ。グイドは「reduceは分かりづらくて嫌いだ」って思ってたらしいが、そりゃお前がそう実装したんだろ、ってのが正直なトコだ(笑)。
この引数順序の不一致、に対してどうすればいいんだろう。
解決策としては
- コールバック関数及びラムダ式の引数の名前を明示的なモノにする。
と言うのがまずは考えられる。x、y等の無味乾燥な名称じゃなく、意味がある名前にすれば混乱する事はないだろう。
あるいは、
- コールバック関数及びラムダ式の引数の順序を逆に置く。
これは特に僕がラムダ式対象で良くやってる事だ。
上の例で言うと、ラムダ式のxとyの順を逆に置く。
>>> reduce(lambda y, x: [x] + y, [1, 2, 3, 4], [])
[4, 3, 2, 1]
そうすれば、reduce本体のラムダ式以降の引数の「並び」と、ラムダ式本体の中の変数の「xとyの順序」が一致するんで混乱しなくて済むだろう。意味は掴みやすくなる筈だ。
さて、ではPythonのreduceで複数のイテラブルを取りたい場合どうすればいいんだろう。
とは言っても悩む必要はない。Pythonはzipを備えてるんで、引数が単一のイテラブル、と指定されていても全部zipを適用した複数のイテラブルを使える設計になっている。
これはreduceに限った話ではなく、mapやリスト内包表記でも同様(※3)で、Pythonの基本設計としては、イテラブルの扱いの基礎にzipが深く関わっている、って事だ。
>>> from operator import sub
>>> reduce(lambda result, x: result * sub(*x), zip([1, 2, 3], [4, 5, 6]), 1)
-27
これは次と同じ結果ではある(※4)。
>>> result = 1
>>> for x in zip([1, 2, 3], [4, 5, 6]):
... result *= x[0] - x[1]
...
...
>>> result
-27
結果は同じなんだけど、例によってreduce版は代入を実はしていない。破壊的変更が無い、んだ。
それにfor版は4行だ。reduceだと1行で済む。4行を1行に圧縮してるのか、1行で済むトコをforは4行を要する冗長さと捉えるか、は人によって違うだろうけど、個人的には1行で済むモノを「4行もかけて書く」ってのは何ともアタマの悪い選択に思える。
これは1行と4行の差、だから大した差に思えないだろうが、こういうのが積み重なって10行と40行、100行と400行、1,000行と4,000行、10,000行と40,000行と、差が開いていくように感じるんだ。
2. reduceは一体何をやっているのか
reduceも畳み込み関数と言われる。概念的にその操作は、例えば
reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) ≡ ((((1+2)+3)+4)+5)
である、って説明は良く見かける。
その辺はまぁいいだろう。「良くある説明」である以上良くあるわけだ。どこでも見かける。
ここではちょっと視点を変えてみる。
reduceとは、イテラブルを使いながらコールバック関数で初期値を「加工」していく関数だ、と捉えられる。
大まかにはこの通りだ。ただし、ちと誤解しやすいトコもあるんで、そこをちと解説していってみる。
まず「加工」だが、初見だと、「加工」と言う言い回しで、あたかも「初期値を破壊的に変更する」と言う印象を与えがちだが実はそれは違う。実際reduceがやってる事は「すげ替え」だ。コールバック関数からの返り値と初期値をすげ替えてる、んだ。
例えば次のような用例を考えてみよう。
>>> reduce(lambda y, x: x, [1, 2, 3, 4, 5])
5
ラムダ式はx、yの2つの引数を取るが、本体ではyが使われてない。要は初期値を全く使ってない。xはリストの先頭から一個一個要素を引っこ抜いて来てるわけだが、ここで明らかになるのは、reduceは粛々と初期値から始めて単に順々にそれらを置き換えていってるだけ、だ(だからリストの最後の要素が返り値となっている)。
次の例を考えよう。
>>> reduce(lambda y, x: y ** 2, [1, 2, 3, 4, 5], 2)
4294967296
今度はラムダ式本体ではxを使ってない。初期値しか使ってないわけだ。
このケースだと[1, 2, 3, 4, 5]と言うリストの中身には全く意味がない。ここではリストは言わば単なるカウンタとして働いてるだけで、[1, 1, 1, 1, 1]だろうが[True, False, True, False, True]だろうが、はたまた[None, None, None, None, None]だろうが何でも良くなってる。
実はこれは計数反復に近く、リストの「長さ」が重要であって中身は問われてない。
この2つの例で何が分かるか、と言うと、xとyの計算への関連付けはreduceが行ってるんじゃなくってコールバック関数の方だ、と言う事だ。
いや、むしろ、reduceは「反復の枠組み」だけを提供してて、実際のトコ、「畳み込み」も何も指定していない。畳み込みを指定してるのはコールバック関数とその返り値の方なんだ。
そう考えると、「reduceはリストを処理しながら畳み込む」と言う説明は実は正確ではない、って事になる。繰り返すがreduce自体は何も指定していない。「何も指定していない」からこそ汎用性があるんだ。
また、コールバック関数は二引数関数である事を要求するが、一方、上で見たように実装上、reduce自体はそれらコールバック関数の2つの引数の関連性に付いても言及していない。ひっじょーにゆっる〜い制限とは言えない制限しか存在しない、んだ。
これらを鑑みると、実は「非破壊的なfor文」ってのがreduceの正体なんだ。
>>> reduce(lambda y, x: y, [1, 2, 3, 4, 5], 0)
0
>>> reduce(lambda y, x: 'hoge', [1, 2, 3, 4, 5])
'hoge'
# 「何もしない」例
また、reduceの説明に於いて、「単一の値へと畳み込む」と言う表現も良く見られる。
ただ、「単一の値」と言う説明も厄介で、実際のトコ、「単一のオブジェクト」と言った方がいいかもしんない。
と言うのも「値」と言われると整数であるとか、そういうモノをイメージしやすい。実際はreduceはリストを返せるのは既に見た通りなんだけど、と言う事は、コンパウンドデータ(合成データ)も返せる、って事だ。「単一の値」にはコンパウンドデータも含まれる。
つまり、Pythonでは、リストに始まって、タプル、辞書、クラスのインスタンス等、reduceで返せる「単一の値」はたくさんある。ついつい用例では「整数」と「リスト」を返り値とした例が多いからそれだけ、って思いがちだけど、その辺には制限がない、んだ。
だから、「単一のオブジェクト」と言った方がより実際に近いと思われる。
3. フィボナッチ数列再び
とまぁ、reduceが強力なのは分かったけど、実の事を言えば、僕自身は「使えるようになった」けど、そこまでreduce、reduceって言ってたわけでもなかったんだ。
言い出したのはむしろ最近かもしんない。
ぶっちゃけ、龍虎氏が、「reduceでフィボナッチ数列が書けないか」って言い出した事が大きい。
正直言うと「んな事出来んの?」って最初は疑念を持ってた(笑)。ところがやってみると簡単に書けちゃったんだな(笑)。これには自分自身驚いた(笑)。こりゃすげぇ、と(笑)。
いや、プログラミングのネタでフィボナッチ数列は頻出ネタなんだけど、「reduceで書こう」ってのは見たことなかったんだよ。だから思いつきもしなかったし、そんな例も知らない。
だからその時も言ったんだけど「プログラミングに慣れれば慣れる程、その時持ってる常識に凝り固まる」って事なんだよね。反省した。そしてこの延長線上に「最初に覚えたプログラミング言語での"常識"からなかなか抜け出せない」と言う悪しきドグマが生じるんだよな。
Pythonのreduceでのフィボナッチ数列実装は例えばこんなカンジだ。
def fib(n):
tp = (0, 1)
return (tp[0],) if n == 0 else tp if n == 1 else reduce(lambda y, x: (*y, y[-2] + y[-1]), range(1, n), tp)
敢えてタプルを使ったのはイミュータブルだから、だ。先にも書いたけど、「初期値を加工してるように見える」が、実際はreduceが行ってるのは初期値の「すげ替え」であって「破壊的変更」じゃない。それを強調したかったが為に敢えてイミュータブルのコンパウンドデータであるタプルを使用した(※5)。
>>> fib(15)
(0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610)
ホンマ、フィボナッチ数列は偉大だ。色んな実装法が考えられる。
そして、プログラミングの教科書なんかもフィボナッチ数列が大好きなのに、フィボナッチ数列のreduceによる実装、なんつーのは見たことがない。
実際検索してご覧よ。無いから(笑)。皆ドグマにハマってるんだ(笑)。
しかし、reduceの使い方を説明するにもフィボナッチ数列は良い題材だと思う。こんなに優れた例示は無いんじゃなかろうか。
ハッキリ言って、reduceでフィボナッチ数列を自力で実装出来ればreduceは免許皆伝、でいいと思う。
と言うのも、これは「現時点での計算」と「過去の値」の両方をreduceではタプルないしはリストで保持出来る、と言う良い実例だから、なんだ。
そう、フィボナッチ数列で現在の値を計算するには過去の値を保持して使わないとならない。
4. ローマ数字を整数値に変換する
さて、本題の「ローマ数字を整数値に変換する」と言うネタをやってみよう。
多分このコード自体はこの人が書いたものじゃなくって、恐らくLeetCodeから引っ張ってきたモノだろう。そして、そもそも酷いコードだ(苦笑)。
以前にも指摘したが、まず、変数名に組み込み関数で使われてる名前を使うべきではない。sumもdictもPythonでは組み込み関数名だが、ここでそれらの名前を変数名として再定義している。これは避けるべき事だ。
関数内で使う際は、スコープでガードされる分まだマシだが、いずれにせよ、「習慣として」不用意に関数名を再定義してシャドウイングする事は避けよう。じゃないと悪習が身につく事となる(※6)。
あとやはり、ローカル変数sumとyを定義して破壊的変更でプログラムを書く、ってのも悪習だ。それはC言語やJavaなんかのプログラミングスタイルであって、Pythonのモノではないだろう(Pythonic、とか言ってる奴らはやりそうだが)。
大体、変数名をsumと名付けたいのなら、総和を取る、って前提なんで、細かい処理をした後、ラストに一気に総和を取る、って方針の方がマシな筈だろう。
しかし、ネーミングと書かれているプログラムが一致してないのなら、「なんかとてつもなくヘタクソな事をやってる」ってのを暗示してるんだ。
さて、ここでローマ数字の読み方を考えよう。
とは言っても基本的にはフツーの数字と同じく、大きい順に左から数値を表す文字が並んでいく。
上のドラクエの例だとXIだ。Xは10、Iは1、と左から大きい順に並んでいる。合わせて11だよな。
今度はファイナル・ファンタジーの例だ。Xは10、Vは5、Iは1なんで、10 + 5 + 1で16だ。
っつーか一番驚いてるのはいつの間にやらファイナル・ファンタジーは16まで出てた、って事だが(笑)。
それはさておき、ローマ数字の特徴で、例えば10以下だとI II III IV V VI VII VIII IX X、と構成要素の集合はI(1)、V(5)、X(10)の3つしか無い。で、特徴的なのはI(1)を使って数え上げてるんだけど、なんつーんだろ、この範囲だとまずは折返し地点でV(5)があって、それに対してI(1)をどう置くのか、ってのが問題として出てくる。
すなわち、4はIVと表現するんだけど、Vの左にIを置くと5 - 1 = 4、って事になるんだよな。一方、Vの右にIを置いていくと加算を示す。しかしゴールのX(10)の直前になれば、またしても「Iの左置きルール」が発生して、IXは10 - 1で9を表す。
この、なんつーの?ある種のピボットって言えばいいのか、その数値に到達する「直前」に「引き算」ルールが登場する、ってのがローマ数字の特徴で、そしてこのルールはローマ数字全体に存在するんだ。
ただし、ややこしいのはそこだけ、で、後はユニット文字、って言えばいいのかね。I(1)とかX(10)とか、C(100)とかM(1000)はただ並べときゃいい、っつー大雑把なんだか何だか良く分からん記法になっている(笑)。良くこんなん考えついたよな(笑)。
例えば今年は2024年なんで、2024をローマ数字で書くと、
MMXXIV
となる・・・2000なんで1000(M)を二個並べて次は20なんで10(X)を二個並べて、最後に4(IV)を付け足す・・・まぁ理屈はそうなんだけど、パッと読み書きはしづらいよな(笑)。
Bewitched(奥様は魔女)のタイトル画面なんだけど、下のローマ数字なんざ一発で分かる?俺には無理だね(笑)。
MCMLXVI
サッパリ分からんだろ(笑)?難問だ(笑)。
これ実は、区切りを入れるとこうなんだ。
M CM LX VI
Mは1000、Cは100なんで二個目は「左置きルール」により1000 - 100で900なんだよ。
Lは50、Xは10なんで、50 + 10で60だ。最後は6。
つまり、1966、1966年制作、って意味になるわけだ。
いっやぁ、読みづらいね(笑)。MGM映画なんか、コピーライトでの製作年表示にローマ数字を良く使ってたと思うんだけど、アメリカ市民ってこれをすんなり読めるのかね(笑)。
さて、こうやって読解していくと、基本的には総和を取っていけばいい、ってのは分かるが、一方、IVとVI、LXとXL、MCとCMみたいに、大小の並びによっては「減算則」が生じる場合がある、と言う事も分かった。
ここで、例えば"MCMLXVI"と言う文字列を「逆順に処理」していく事を考える。つまり"IVXLMCM"と言う文字列に対して、先頭から処理していく、と考える。この並び順だと原則「小さい方から徐々に大きな方に」数を加算していくわけだが、中に数値の大小が逆順になってる場合がある。その時には減算則が生じる、と言う事だ。
つまり、累積値と前回加算に使った数値の2つの記憶域を持たなきゃならない、って事になる。
初期状態:
0 <- IVXLMCM
0
第1ステップ
1 <- VXLMCM
1
第2ステップ
6 <- XLMCM
5
第3ステップ
16 <- LMCM
10
第4ステップ
66 <- MCM
50
第5ステップ
1066 <- CM
1000
ここまで順当に加算だけで来てるが、ここで「前回加算に使った値」1000よりC(100)が小さい、と言う事態が生じる。従って、ここでは加算ではなくって1066から100を引かないとならない。
第6ステップ
966 <- M
100
第7ステップ
1966
1000
これで終了、だ。
繰り返すが、記憶域は累積値用が一つ、「前回加算に使用した値」を保持してる記憶域が一つ必要で、「前回加算に使用した値」が今から加算しようとしている値より大きいか小さいかチェックして、小さかった場合のみ減算を実行すればいい。
これをreduceで書く場合、「初期値」を(0, 0)と言う2要素のタプルにし、片方を累積用の記憶域、もう片方を前回使った値にすればいい。
つまり、上の例の場合、
>>> d = {'I': 1, 'V': 5, 'X': 10, 'L': 50, 'C': 100, 'D': 500, 'M': 1000}
>>> reduce(lambda y, x: (y[0] - d[x] if y[1] > d[x] else y[0] + d[x], d[x]), reversed('MCMLXVI'), (0, 0))
(1966, 1000)
と書けば基本的に関数化が可能だ。
なお、こういった場合、上のラムダ式の構成を見れば分かるけど、今回加算したい値、前回加算した値、の大小を比較した結果、変わるのは演算子+/-だけ、なんですげ替えたいのはそこだけ、なんだよな。
ところが、中置記法なんでそのすげ替えが上手く行かない。
>>> from operator import add, sub
>>> reduce(lambda y, x: ((sub if y[1] > d[x] else add)(y[0], d[x]), d[x]), reversed('MCMLXVI'), (0, 0))
(1966, 1000)
Pythonでは関数がファーストクラス・オブジェクトだと言う事を思い出そう。
sub if y[1] > d[x] else add
この部分で、条件に従って「減算」と言う操作か、「加算」と言う操作が適用されるか、が決まる。
そしてそれら操作に対して2つの引数(y[0]とd[x]だ)が必要なんで、関数の計算式は次のようになる。
(sub if y[1] > d[x] else add)(y[0], d[x])
結果、sub(y[0], d[x])とadd(y[0], d[x])を纏めて記述出来る。Lisp由来のテクニックだが、関数がファーストクラスオブジェクトだ、と言う事はこうやって「短く書ける」事を意味する。
あとは、辞書型から値を得るd[x]の重複を避ければいいだけ、だが、Lispと違ってPythonのラムダ式は複文を取れない。
(let ((d (hasheq #\I 1 #\V 5 #\X 10 #\L 50 #\C 100 #\D 500 #\M 1000)))
(foldr (lambda (y x)
(let ((n (hash-ref d y)))
`(,((if (> (cdr x) n)
-
+) (car x) n) . ,n))) '(0 . 0) (string->list "MCMLXVI")))
#| Lisp(Racket)での例。
ラムダ式内でハッシュテーブルdからの参照値を変数に束縛出来る。 |#
結局、Pythonではreduce内にラムダ式を直接埋め込むより、ローカル関数を作ってそれを埋め込んだ方が良い。
完成形はこのようになるだろう。
最後のreduceはタプルを返すので、計算結果である第0要素「だけ」を返すように、インデックスの[0]を付けるのを忘れないようにしよう。
「ローマ数字を整数値に変換する」と言うネタが、reduceの使用例として、reduceの理解の助けになれば幸いだ。
※1: 当時のGoogleは輝いていた。ハッカーが魅惑的なツールを作ってはバンバン公開するイカした企業だった。
一方、現在のGoogleは往年の見る影もない。今のGoogleは何も作らず、むしろ作ったサービスを片っ端から潰す企業に落ちぶれた。
※2: 以降はfrom functools import reduceしているもの、とする。
※3: リスト内包表記はイテラブルを複数取ることが可能だが、その場合、演算結果はデカルト積が基本となり、殆どのケースでは貴方が望んだ計算結果にはならないだろう。
※4: xの前に付いてるアスタリスク(*)は乗算記号ではなくってアンパック演算子だ。リストの角括弧を強制的に排除する。
Lispだと例えば、
> (apply - '(1 4))
-3
と書きたいトコロをPythonでは(from operator import subして)
>>> sub(*[1, 4])
-3
と記述する。
実際、Python 2.x 時代ではこれはLisp宜しく、
>>> apply(sub, [1, 4])
-3
と記述してたが、Python 3.xになってからapplyは廃止され、全面的にアンパック演算子が活躍する事となった。
※5: n = 0の場合、
>>> fib(0)
(0,)
とコンマが付いて来るが、これはバグではなく、意図したモノだ。
と言うのも、Pythonでは、単項のタプルを作ろうとすると、単にその値になってしまう、と言う極悪仕様になっている。
>>> (0)
0
実はPythonの場合、単なる単項オブジェクトとタプルの境界線が曖昧なので、敢えてタプルを強調するには、このようにコンマ付きを括弧で括らないとならない。
※6: Python付属のIDLEでは、組み込み関数名は色替えして表示してくれるんで、こういう不用意な事を避ける事が出来る。
紫になってるのが組み込み関数だ。
プログラミング初心者には別途IDEは必要ない、って言ってるのはこういう辺りなんだ。そのIDEはこうやって(一種の)警告を出してくれるのか、と。
確かに本格的な開発ではIDEは便利なんだけど、プログラミング初学者向けを考えた時に、こういった「組み込み関数名」を教えてくれるのか、と。
通常、汎用IDEだと別途またプラグインが必要になったり、結構面倒くさい事になるんじゃないだろうか。
IDLEだと、全面的なインデントし直しとかが面倒くさくって、そういう操作は汎用IDEの法が上手いこと色々とやってくれるのは重々承知してはいるが、やぱり「学習用環境」として考えた場合、IDLEはそれでも群を抜いてると思っている。