ひびのログ

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

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

最近機械学習でPythonが日本でもにわかに話題となっていますが、ワタシパイソンスキジャナイ......
その理由をつらつらと書いていきたいと思います。

注意

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

1. 2系と3系

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

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

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はmodule.exportsしないとimportできないので理にかなっているかなと。

選外

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

おわりに

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

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

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

「上で触れた内容以外にもあるよ!」とか、「それはこういう理由があるよ!」とかあれば教えていただけると幸いです。