ひびのログ

日々ではないけどログを出力していくブログ

Python の好きになれないところ 5 選

まえがき

最近機械学習で Python が日本でもにわかに話題となっていますが、正直 Python は好きじゃないです。 その理由をつらつらと書いていきたいと思います。

注意

あくまで Python という言語に対する個人的な感想であり、Python に関わる方々を批判しているわけではありません。 「そう思う人もいるんだー」程度に見ていただけると幸いです。

1. 2 系と 3 系

よく言われることだと思いますが、移行するなら早く移行してほしいです。 すぐにでも 2 系を deprecated にして、どんどん 3 系に移行してください。 2 系でしか使えないライブラリとかがあると萎えます。 2 系の EOL が 2020 年らしいので、そこまでの辛抱……?

Python 界隈では、2 系から 3 系に移るのはゆっくりでよく、EOL を迎えるまでに移行するという方針みたいです。 いや、絶対に移行終わらないですよね。

とりあえず、3 系で学習した後にわからないことがありググったときに 2 系の情報が出るのは、とても紛らわしいのでやめていただきたいです。

自分がメインで使っている JavaScript でも全く同じことが言えるので深掘りはやめておきます

2. メソッドと関数の使い分け

例えば Java なら、ソートしたい場合は、list.sort((a, b) -> a - b) とします。List オブジェクトのメソッドを呼び出します。
あるいはリストの長さを取得しようと思ったら、list.size() とします。こちらも List オブジェクトのメソッドを呼び出します。

一方Pythonの場合、
リストをソートしたい場合は、list.sort() とします。リストのメソッドを呼び出しています。
一方リストの長さを取得する場合は、len(list) とします。関数にリストを渡しています。

Java ではリストに対する操作は List のメソッドを呼べばよかったのに対し、 Python ではメソッドか関数か判断してコーディングしなければいけません。 本題とはちょっと違いますが、自分は Java を書くときに、オブジェクト名.まで打つと表示されるメソッド一覧から選ぶことがあるため、 それが通用しないとなると途端にコーディング速度が落ちます。

要するに、いちいちどっちか自分で判定したくないんです。


実は Python には sorted(list)list.sort() の 2 種類のソート方法があります。 前者は関数、後者はメソッド呼び出しですね。 「ただでさえわかりづらいのに 2 種類あるのかよ!」と言いたくなる気持ちはありますが、これには合理的な理由があります。

関数呼び出しの方は「副作用なし(list が変更されない)」、メソッド呼び出しの方は「副作用あり(list が変更される)」です。 つまり、使用するときには new_list = sorted(list)list.sort() のように、使い分けが必要です。

これは先程のソートと長さの取得でも当てはまります。 list.sort() は副作用ありなのでメソッド、len(list) は副作用なしなので関数となっています。 わかりやすい! ここは思わず「なるほど」となりました。

ただし。

len() は副作用なしの関数と言いましたが、内部では __len___ メソッドを呼び出しているようです。

(参考:Pythonを書くための最低限の文法メモ - Qiita

……関数とメソッド分けてる意味ないじゃん?


と Python ばっかり責めていますが、Java も Java でひどいです。 list.sort() と同様にソートする Collections.sort() があります。 これ、両方とも副作用ありで、list オブジェクト自体が変更されます。 Collections の JavaDoc にあるように、

この実装は、指定されたリストと null コンパレータを使用し、List.sort(Comparator) メソッドに従います。

とあるため、なんで存在するのかわかりません。

ちなみに Scala だとリスト(Seq)は基本 immutable なので、副作用がないメソッドしかありません。 mutable のリストのソートについては知りません。使わないので。

3. リスト内包表記

これもわりとよく言われるかなと思います。

[ i * 10 for i in range(100) if i % 3 == 0]

こんな感じのやつ。

個人的には

for i in range(100):
    if i % 3 == 0:
        yield i * 10

こっちのほうが読みやすくて好きです。 でもこう書くと、「Python らしくないー」とか「実行速度がー」とか言われるのです。 これも慣れですかね?

4. インデント構文

自分は C → Java → JavaScript → Scala → TypeScript みたいな感じで言語を学んできたので、ブラケットで囲むのが普通なんですよね。 なのでインデント構文というのが好きじゃないです。

ちなみに Ruby とかの if-endif 構文もあまり好きじゃないですが、シェルスクリプトを最近触っているので、なんとなく慣れてきました。 インデント構文も慣れればいいんですかね。

ただ、これにはわりと明確な理由があります。 前述したリスト内包表記ですが……

[ i * 10 for i in range(100) if i % 3 == 0 ]


一体どこで改行できるんだ……!?


今この長さだから大丈夫だけど長くなったら見づらいし、 インデント構文だから変に改行したらプログラム実行できなくなりそう……

初見だとこうなりませんか?

ちなみに試したところ、for とか if の前で改行して、インデントあり・なし両方問題なく動きました。 ついでに最後の ] だけ改行しても動きました。

インデント構文が嫌だという意見に対して大体言われるのが、「誰が書いても同じようなプログラムになるから見やすい」というものがあります。

……いやいや、それはコミット前に Lint とかフォーマッタとかに任せればいいじゃないかと。

構文レベルで縛る必要はないんじゃないかと思います。 人それぞれ書きやすいフォーマットは違うわけですし、特に個人開発では好きにさせてください。

5. if __name__ = "__main__"

import されたときに実行されないようにするための魔法の呪文ですね。

これは発想が逆かなーと考えています。

すべて import できるようにするのではなく、import される側が許可したものだけが import できるようにすべきではないでしょうか? 何でもかんでも import できる現状では、モジュールとして使われると思っていなかったファイルまでモジュールとして使われるかもしれません。 「そんなふうに使われるなんて思ってなかった!」というのは、プログラマにとっては結構馴染み深い問題だと思っています。

実際 JS は export しないと import できないので理にかなっているかなと。

選外

  • from xxxx import yyyy の順番が英語的に変。
  • JS や Ruby でも似たようにかけますが、Python のスライスはプラス一癖。Scala の記号メソッド乱立に似た何かを感じます。
  • モジュールをディレクトリ分けするときに __init__.py を置かないといけないのが不便。
  • 機械学習でほぼ強制的に触らされるのが嫌です。

おわりに

選外の最後に書いたように、最近機械学習で触る機会が多かったので Python が槍玉に挙げられましたが、 多かれ少なかれ、これまで学習した言語で不満がないものはありません。

また、Python にもナイスだと思うところはありますし、 上で紹介した内容でも時と場合によってはナイスだと思うこともあります。 2 系と 3 系は時間が解決しますし、初学者の方にはインデント構文が力を発揮します。

触ってみないとわからないことが多々あったので、実際に書いてみるということは大事だなと思いました。

ここまでの内容は私の感想・考えですので、合っているかは保証しません! また、「上で触れた内容以外にもあるよ!」とか、「それはこういう理由があるよ!」とかあれば教えていただけると幸いです。