見出し画像

Retro-gaming and so on

プログラミング初学者がPython入門書を選ぶコツ

前回、どーしても初めてやるプログラミング言語はPythonにしたい、処女をPythonに捧げたいの!って人には

「洋書の翻訳本を買え」

と言うアドバイスを行った。
理由は、もう一度書くが、日本で翻訳しよう、となった時点で、既に欧米で評判になってて、実績が出てるから、と言うものである。
最近出版ブームになってる、国内製の「粗悪な」Pythonプログラミング入門書、の類よかマシな結果になるだろう、と言うのが前回の論の要点、である。
そしてC言語脳で書かれたPythonコードに対しては警戒に警戒を重ねても重ね過ぎる事はない、と。C言語脳で書かれたPythonコードはクソで汚い。生粋のPythonistaが書いた本があればいいのだが、そのテの本は極めて少ない。
なんせ、Python本の出版はバブル状態なので、「大してPythonを理解してもいないし愛してもいない」C言語脳がうようよ蝿のようにたかってメチャクチャな書籍が出版され続けてるのだ。
これが前回のあらすじ、である。

そうは言っても「日本で書かれた本が良い」って人はどうしてもいるだろう。翻訳文って硬いしな〜、みたいな。
そこでそう言った場合に対して、「良書」を判断するコツを書いておく。
具体的な書名は挙げないし(※1)、そもそも最近はPython本の質に辟易してて僕自身はチェックしていない。
従って、ここに挙げてるヒントに従って、自ら書店に赴き、自身で判断してもらいたい。

ところで。
「ヒント」とか言うと。
このブログを読んだ事がある人は、

「リスト内包表記でしょ?」

とか言う人が出てくるだろう。当たりだ(笑)。
とは言ってもそこはメインではない。
どっちにせよ

「リスト内包表記は可読性を下げます」

とか書いてる本はぶん投げていいけどな(笑)。



分かるか?可読性を下げるどころか上げるのがリスト内包表記なんだよ。
C言語脳とPython作成者達のどっちが正しいんだろう?言わずもがな、後者である。そして彼らはその可読性を武器としてむしろ内包表記を増やしている。そしてPythonistaはそれに対して賛同してるんだ。
要するにC言語にはリスト内包表記、と言う便利な機能が無い、って一点だけ、でC言語脳は「可読性を下げる」と言ってるわけだ。自分が慣れてないから(※2)。「C言語は基礎」って妄言を盲信してるから。
別にプライベートでリスト内包表記を使わないでコードを書く、ってこたぁ悪くない。勝手にすれば良い。ただし、「プログラミング初心者用の本を書く時に余計な事を書くな」っつってんだ。再帰の話でも何度も書いたが、そもそも初学者に余計な偏見を植え付けるのは百害あって一利なし、っつってんのである。
C言語脳にはリスト内包表記は難読でもネイティヴな「将来のPythonista」は全くそういう事を思わないかもしれない。その可能性を潰す、ってのを害毒と言わずして何と言うんだ。
猛省すべきだ(※3)。

とまぁリスト内包表記に対しては「当たり前の事」として置いておいて。
次から「こんな本は買ってはいけない」と言う4点を挙げる。本屋で立ち読みしながらチェックする項目だ。

1. IDEを導入させよう、とする本はダメ

Pythonはオールインワンなのに、別個のIDE(例えばVSCodeとか)を導入させよう、なんつー本はダメな本だ。そういう本は買わなくて良い。
プログラミング初心者がIDEを導入するのはメチャクチャ敷居が高い、のである。しかしバカは隙あらば自分が愛用してるIDEを初心者に薦めてそのIDEの信者を増やそうとする。殆ど宗教勧誘の様相である。
結果、初心者は設定に難儀して教えて!gooなんぞにヘルプを投げてくるのである。一方、それを薦めたヤツは絶対「困った初心者をサポートしない」。無責任極まりないのだ。
IDEの設定は一般に、コンピュータにかなり慣れないと無理だ。それはプログラミングを始めてから「知っていく」事になってて、要するにズブの素人には難易度が高すぎるんだ。だから初心者に親切な設計になってる言語はIDE込みで提供されている。Pythonはそういう言語なんで、わざわざ余計な手間をかけさせる必要が全くないんだ。
これは僕自身の経験から言ってる。僕は今Emacsを使ってるが、これも最初に導入した時はメチャクチャ苦労したのだ。「慣れてる人」には当たり前だけど、プログラミング初心者にとってはそうではない、のだ。


Emacsで走らせてるPython用開発環境。

僕はよっぽどじゃないと誰にもEmacsは薦めない(※4)。それなりにコンピュータに慣れてないと導入手順はメチャクチャ敷居が高い、と言うのが「既に」わかりきってるからだ。導入さえすれば絶大な威力を発揮するが、プログラミング初心者にとってはそのメリットよりも導入に纏わるトラブルのデメリットの方が遥かに大きいだろう。
Emacsだけではない。どのIDEでもそうなんだ。PyCharmだろうがSpyderだろうがな(そして設定をやってるウチにハマって分かんなくなって教えて!gooに泣きついてくるのである)。
無謀な策は止めといて初心者にはPython備え付けのIDLEを使わせろ。そうしてない書籍は宗教勧誘絡みのダメな書籍である。
(ただし、本の最後にAppendixとしてIDEを色々と紹介してる、ってな場合はこのケースには当てはまらない)

2. なんでもかんでもprintしてるようなコードを書いてる本はクソ

ここからC言語脳の特徴を書いていく。
まず最初は「何でもかんでもprintしてる」ようなコードを書いてる本はクソだ、と言う事。本に載ってるコードのprint比率を見てみれば良い。print塗れだったらその本はC言語脳による粗悪品である。
このブログでは既にお馴染みになってるが、プログラムを書く、と言う行為は「どういう返り値を返すか」設計する事であって、出力する事ではない。
一方、C言語はとにかく「書いてるプログラムの結果」が一発で分からない言語で、結果、printf塗れのコードをとにかく書かなければならない(配列なんざ使った日にゃあなんと配列上をルーピングして要素を一個一個表示しなきゃなんない!)。これはPythonに持ってくると単なる悪習だ。
C言語脳はぶっちゃけ、インタプリタでの開発に慣れてない。結果、クソみたいなprintの垂れ流しになる。

残りはリスト操作だ。

3. リストにforを適用して要素を引っ張ってくる時、rangelenを使ってるコード例を出してる本はゲロ

C言語は配列にアクセスする際にとにかく要素番号を使わないとならない。
従ってC言語脳は、リストにforを適用したルーピングを目論む時、次のようなコードを初心者に提示する。

ls = [1, 2, 3, 4, 5]
for i in range(len(ls)):
 ls[i]

これはゲロなコードだ。吐きそうになる。
Pythonでは次のように書くべき。

for i in ls:
 i

実際、PythonにはC言語で言うforは存在しない。どっちかと言うとC++やJavaの拡張forに近いのだ。
「C言語ユーザーがJavaを書けば、Javaユーザーより綺麗なコードを書ける」と主張するが、Pythonに於いてはJavaユーザーの方が後者をすぐ思いつくだろう。
そして、実際のPythonプログラミングに於いて、チマチマと数値単体同士を足したり、あるいは掛けたり、なんつー、「如何にもC言語入門書に書いてるような」プログラミングは出てこない。大概はsum関数やfunctools.reduceで解決出来てしまう。その辺のショボいプログラミングはPythonのbuilt-inでほぼ解決可能なわけで、わざわざ自分でforを回す必要なんざねぇのだ。
一番良いのはリスト内包表記を使う事だ、と強調はしておくが、そうじゃなくても、リストをforするスタイルの場合、「要素番号をアテにしたような」プログラムを例示してるC言語脳な本はゲロレベルである。いや、ゲロ以下だ。決して真似してはならない。

4. リストを紹介する章で、如何にも親切じみた顔でリスト操作メソッド一覧表なんざ挙げてる本はバカ

これも良くある。クソして寝てろ、って言いたいわ。
プログラミング初心者に言っておく。Pythonでのリスト操作メソッドなんざ使うな。理由はハッキリしている。そんなん使えばメンド臭いハメに陥るから、だ。

例えばネットでテキトーに検索したけど、こういうのはダメな例だ。

# ダメなやってはいけない例
a = [1, 2, 4]
a.append(7)
print(a)

[1,2,4,7]

こう書くべきなんだ。

# 望ましいスタイル
>>> a = [1, 2, 4]
>>> a + [7]
[1, 2, 4, 7]
>>>

何故か?
この2つの違いは、aと言うリストそのものを書き換えるか、と、aと言うリストには全く手をつけずに計算結果を得ようとしてるのか、である。後者の場合、a自体には全く手が入ってない。

>>> a = [1, 2, 4]
>>> a + [7]
[1, 2, 4, 7]
>>> a
[1, 2, 4]
>>>

前者を破壊的変更と言う。そして破壊的変更は見つけづらいバグの元、になるんだ。前者のappendと言うメソッドはバグの元だ。
他、これもダメ。

# ダメなやってはいけない例
a = [1, 2, 4]
a.insert(2, 7)
print(a)

[1,2,7,4]

望ましいスタイルは以下のようなモノだ。

# 望ましいスタイル
>>> a = [1, 2, 4]
>>> a[:2] + [7] + a[2:]
[1, 2, 7, 4]
>>> a
[1, 2, 4] # aは全く変わってない
>>>

これもダメだ。

# ダメなやってはいけない例
a = [1, 2, 4]
del a[0]
print(a)

[2,4]

こう書くべき。

# 望ましいスタイル
>>> a = [1, 2, 4]
>>> a[1:]
[2, 4]
>>> a
[1, 2, 4] # aは全く変わってない
>>>

最期にこれもダメだ。

# ダメなやってはいけない例
a = [1, 2, 4]
b = [5, 7]
a.extend(b)
print(a)

[1,2,4,5,7]

当然、こう書くべきだよな。

# 望ましいスタイル
>>> a = [1, 2, 4]
>>> b = [5, 7]
>>> a + b
[1, 2, 4, 5, 7]
>>> a
[1, 2, 4] # aは全く変わってない
>>> b
[5, 7] # bは全く変わってない
>>>

C言語脳は「データを直接改変する」リスクに無頓着なんだ。
何故ならC言語のプログラミングスタイルだと、どうあっても「配列要素を直接書き換えないと」マトモにプログラミングが出来ないからだ。そしてだからこそデバッグが難しいプログラムになり、連中は毎度毎度苦しむわけだ(※5)。
一方、Pythonはそういう設計にはなっていない。しかし、C言語脳はリストのメソッドを見つけると嬉々として破壊的変更に勤しむわけだ。
もう一度繰り返す。C言語脳のプログラミングスタイルに染まるな。そのための第一歩としてリストに付随するメソッドをまずは使わない、事が大事だ。そしてリストに付随するメソッドを使うように薦めてる本はバカなんで、買ってはならない

でも、こう書くと不思議だろ?なんでPythonは「使っちゃならない」メソッドなんかを用意してるのか、と。どうして同じ効果(に見える)の方法を2つ用意してるんだ、と。
いや、実は「使っちゃならない」ってのは確かに言い過ぎなんだ。ただし、これらリスト操作のメソッドは使い時がある、って事なんだよ。そしてそれは決してプログラムの書き始めではないんだ。プログラムを書き終わった後がそれら破壊的変更メソッドの出番なんだ
ちと不思議だろ?プログラムが完成した後に使うって?変だろ、と。
それはこういう事だ。

貴方がプログラムを書き終わった後、バグ取りも終わり、思ったようなプログラムになりました、と。
ただ、「動作がモッサリしてんな〜」と思ったとする。
つまり、場合によっては貴方の書いたプログラムの「リスト操作」に貴方のコンピュータのリソースがかなり割かれてるんじゃないか、と疑ったとする(本当にそうかどうかは分からない)。
いずれにせよ、まずは「貴方のプログラムを遅くしている」原因を探らないとならない。そういう時に使うPython組み込みのソフトウェアをプロファイラと呼ぶんだ(※6)。
プロファイラは貴方の書いたプログラムを受け取って、どの関数が何回呼ばれて、実行速度がどの程度なのか、貴方に報告する。そのデータを見つつ、プログラムを遅くしてる関数の「問題箇所の」修正に入らないとならない。
んで、仮に「望ましいスタイルで書かれた」リスト操作が原因だった時(当然そうじゃない場合もある)、それと挿し替える為に用意されてるのが、平たく言うとリスト操作メソッドなんだよ。分かる?破壊的操作を伴うリスト操作メソッドの存在ってのは原理的には代用品なんだ。最初から使うモンじゃないの(※7)。
一般的に言うと、破壊的変更は使用メモリ量が少なくなる傾向があるの。
ただし、だ。リストの破壊的変更をしなかった部分が本当にプログラムを遅くしてる原因(こういうのを「ボトルネック」と呼ぶ)なのかどうかはプロファイラを走らせてみないと分からん。っつーか、プログラムを書き始めた時に予測可能な範疇にないわけ。
結果、どっちにしても、「一旦プログラムを組み上げないと」ボトルネックがどこになるかはサッパリ分からんわけよ。んでプロファイラを使いつつ、書いたプログラムをチューニングする過程をプログラムの最適化、と呼ぶ。
O.K.?

プログラムを書く->プログラムのデバッグをする->プログラムを一旦完成させる->プロファイラにかける->プログラムを最適化する->またプログラムのデバッグをする->プログラムの完成

プログラムを完成させるまでには結構長い道のりがあるわけだ。
そして最初に書いた通り、破壊的変更は予期せぬバグを生む。従ってプログラムの最適化の過程では相当慎重に「破壊的変更」に置き換えないとまたもや思わぬバグを生み出す原因になる、って事だ。

んでもう一度言うけど、「プログラムの書き始めではどこがボトルネックになるのか」サッパリ予測は付かない。
計算機科学者のドナルド・クヌースは次のように言ってる。

  • 早すぎる最適化は諸悪の根源
よって、最初から破壊的変更であるリスト操作メソッドを使おう、なんつーのは論外なわけ。だからプログラミング初心者はこの辺のメソッドに手を出すな、っつってるわけよ。
分かりましたか?

しっかし、どういうわけか、C言語脳はそういう開発プロセスを全く知らんような事をヘーキでPython入門本に書くんだよなぁ・・・・・・。不思議だ。

と言うわけで、もし日本で書かれた初心者向けのPythonプログラミング入門書をどーしても欲しい、ってな場合。以上に挙げた4点をチェックする事。
ぶっちゃけ、この4点をクリアしました!なんつー本があるかどうかは知らん。そしてこの4点をクリアしてない本は、まぁ、買う価値はないわな。むしろ、他にもなんか「おかしな事を」書いてる可能性さえある、って事だ。この4点をクリアするだけ、でも実際問題かなり大変なんじゃねぇか、って予想してる。
クリアしてる本が一冊も無かったらどうすんのか?諦めて洋書の翻訳本を買いなさい。この4点もクリア出来ないような「入門書」なんて買っても後々苦労するだけ、なんで、同じ金払うんだったら洋書の翻訳本を買った方が絶対マシな結果になると思う。
じゃなかったら、プログラミングをやるのは取り敢えず諦めて、握りしめた金で「鬼滅の刃」買ったり、あるいは「呪術廻戦」のマンガ買って帰って読んだ方がよっぽど有意義な時間を過ごせると思う。
もっとも俺は呪術廻戦読んだこたぁねぇけどな(笑)。でもジャンプのマンガなら大丈夫だろ。IT系出版社の出版物より遥かに高い信用度があるんだから。

※1: 過去、Pythonを教える本ではなくって、「Pythonを使って」プログラミングを教える書籍としての最高傑作が、「Pythonで学ぶプログラム作法」の名を冠して販売されていた。古いPython(1.x)に準拠してるが、非常に分かりやすく書かれた良書だったし、「Pythonを使い続けろ」と言うような視点ではなく、巻末にPython以外の各種プログラミング言語を紹介してて、是非とも色々な言語を触ってみるべきだ、と結んでいる。
残念な事に、日本の出版元である桐原書店は、技術書を販売してた「ピアソン・エデュケーション」と言うレーベルから2013年に撤退してしまい、この本は廃刊となってしまったのだ。
ただし、この本の原作は英国にあるWebサイトで、そちらは現在でも健在である。
今現在はPython3をベースにした文書になり、また、「Pythonにこだわらない」と言うかつての視点も健在で、結果、スゴい事にPython3、JavaScript、VBSの3つの言語を「同時に使って」プログラミングを教えているとんでもないサイトになっている(笑)。
英語が読める人には超オススメの「プログラミング入門サイト」と言えるし、日本の「入門サイト」のクオリティの比じゃないのは間違いない。

※2: いつぞや、某サイトで、多分そこそこの年齢のCプログラマが登場し、「部下には再帰を使うな、と厳命している。何故なら俺が再帰が分からないからだ。」とか冗談めかして言ってたが・・・・・・あ~、うん、まぁ、要するにそういうこった(笑)。
言っただろ?C言語の存在ってのは事実上、「若い奴らにマウントを取りやすい」ってのが殆ど唯一の利点となってる。
そして別にプログラマの共通性質が「理論屋だ」ってワケじゃねぇんだよ。そして何度も言うけど、「プログラミングと理論性は全く関係がねぇ」ってこった。

※3: ただし、リスト内包表記の挙動が信頼出来ない、と言う意見には一考の価値はある。事実このブログでもこういう現象を書いた。リスト内包表記は「最適化」が施されている為、思わぬ挙動をする可能性はいつでもある、わけだ。
ただし、問題の本質はリスト内包表記と言う構文の存在ではない。単にPythonは「仕様=実装」の言語であり、動作保証する仕様文書が全くない、辺りにある。つまり、おかしな挙動をしても「それがバグだ」と言う根拠を探す事が不可能なのだ。従って「実装を理由」に言い訳をする事はいつでも出来る。
PythonがLisp、Ruby、JavaScript、C言語に比べると信頼しきれない理由はこれだ。ここに挙げたPython以外の言語には曲がりなりにも「文書」として仕様があり、それが動作を保証してるわけだ。
正確に言うとLispには標準、と言える仕様はないが、少なくとも実装が理由でおかしな動作をする、って事はまずあり得ない。

※4: 実際、星田さんにEmacs薦めちまったが、結果どうなのかドキドキ心臓バクバクである。
ぎっくり腰の原因じゃなければ良いのだが(それとも「医者の不養生」に近い?)。

※5: もっとも、苦しむのが嬉しい、って奇特な性癖を持つ方々もこの世にいる、ってこたぁ事実である。

なお、厳密な話をすると、C言語の配列は他の、もっと抽象度の高い言語におけるデータ型でさえなのだ。ハッキリ言えばC言語の配列はパソコンに搭載されているメモリそのもの、であってどうにも動かしようのないものだ。そこに剥き出しで転がっている物体だ。そして動かしようのねぇものは書き換えるしかねぇわけだ。
従って、原理的にはC言語の配列は関数の返り値として返せない。と言う事は、基礎的な使い方に絞ると、「動かせないなら動かさなきゃいいじゃない」とばかりに、配列を大域変数として扱って「常時書き換え対象とする」、つまり破壊的変更の対象にするのがストレートで明解な手法、って事になる。いや、マジな話で、だ。
言い換えると、データ型でさえないような原始的なモノの「しょーもない扱い方」をPythonのリスト(こっちはキチンとした高級言語のデータ型だ)に適用しよう、なんつーのがそもそも発想としては間違っているのだ。
C言語脳、は実の事を言うとデータ型が存在する意味が分かってないのである(まぁ、極論、C言語は数値しかねぇ言語だからな)。

※6: Pythonのプロファイラに関しての説明は公式ドキュメントに書いてある
なお、今時のモダンな言語処理系には、プロファイラはデフォルトで含まれてる、と考えて良い。
現代ではデバッガとプロファイラは二大開発必須ツールとなっている。

※7: 他にも並べ替えのsortメソッド、リストを反転させるreverseメソッドも「プロファイラをかけた後に、ボトルネックになってたら使う」メソッドであって、最初から使うモノではない。非破壊的なsorted関数、及びreversed関数を最初は用いるべきで、そっちの方が安全である。
  • Xでシェアする
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

最近の「プログラミング」カテゴリーもっと見る

最近の記事
バックナンバー
人気記事