fuzzy study

仕事・趣味で勉強したことのメモ

python使い始めて1年半経ったので好き嫌いをまとめてみる

python使い始めて1年半経ちました。(python3です)
機械学習関連に加え、ちょっとした作業のためのツールとして使ったり、趣味でwebバックエンドとして使ったりしました。

現時点で感じているpythonの好き嫌いをまとめてみます。
(それほど多くの言語のことを知っているわけではないので、これから意見が変わることは大いにありそうです。)

pythonの好きなところ

pythonに限ったことではない要素が多いかもですね。。

とにかく読みやすい

pythonの最大の特徴であるインデントによるコードブロックにより無駄な行がなくコンパクトになることをはじめ、 細かな記法が基本的にシンプルに設計されている印象を受けました。

読みやすさを重視したpythonの最大の利点だと思います。教育用に薦められるのも頷けます。

リストのインデクシング

  • リストに対する添え字が[開始:終了:間隔]と指定できる
  • マイナス値で末尾からの数を指定できる

ので、スライスやリバースが簡単。

s = "hello world!"
print(s[-2]) # d
print(s[::-1]) # !dlrow olleh

swapの書き方、比較の書き方

C言語から来たのでうれしい仕様でした。

# swap
a, b = 0, 1
a, b = b, a # a: 1, b: 0

# 比較
0 < a < 10 # True

for-else

for文内でif文で判定し、breakで処理を中断するケースは多々あります。
この時、一度もbreakに引っかからなかったとき「だけ」行いたい処理を書くとき、elseが使えます。

これもC言語から来た私は感動しました。

def func(names):
    for s in names:
        if s.startswith("ぼ"):
            print(s)
            break
    else:
        print("ぼで始まるやつなんかいないよ!!")

func(["たかし", "あきら", "すぐる"]) # ぼで始まるやつなんかいないよ!!
func(["たかし", "あきら", "すぐる", "ぼく"]) # ぼく

リスト・辞書展開

関数にリストや辞書の各値を展開して渡すことができます。何かと便利です。

def func(num, msg):
    print(msg * num)

l = [3, "hello"]
d = { "num": 3, "msg": "hello" }
func(*l) # hellohellohello
func(**d) # hellohellohello

変数への代入ならカンマ区切りで展開できます。

l = [1, 10, 100]
a, b, c = l

リスト内包表記

リストに対する繰り返し処理を行い新たなリストを生成するコードをワンライナーで書かけます。

l = ["バッツ", "レナ", "ガラフ", "ファリス"]
count = [len(s) for s in l]
print(count) # [3, 2, 3, 4]

なお、辞書やジェネレータを作る内包表記もできます。

d = {s: len(s) for s in l} # {'バッツ': 3, 'レナ': 2, 'ガラフ': 3, 'ファリス': 4}
g = (len(s) for s in l)
next(g) # 3
next(g) # 2
next(g) # 3
next(g) # 4

デコレータ

python以外にはなさそうな機能で、デコレータというのがあります。
主に関数のラッパ関数を作るときのシンタックスシュガーとして使われ、Flask等のライブラリを使うとその良さがわかります。
(より正確には関数を引数に取る関数を書く際のシンタックスシュガーであり、工夫次第でいろいろできます。マーキングとか。)

Flask公式トップのサンプルコード

from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

自分でコードを書くときに使う機会があるかというとほとんどないのですが、 ライブラリを使うときには結構よく出てきて、使いやすいので、pythonが好きな理由の一つになりました。

ジェネレータ

ジェネレータは、イテレータとして使える関数のようなイメージです。
returnではなくyieldで値を返すと、次にその関数を呼んだときに続きから実行してくれます。

def generator_sample(count):
    for i in range(count):
        yield f"hello, {i}!"

hello10 = generator_sample(10)
print(next(hello10)) # hello, 0!
print(next(hello10)) # hello, 1!

などに使えるイメージです。

ダックタイピング

以前は静的型付けの言語ばかりやっていたので、型宣言がないとかどうやってプログラム書くの?と思っていたのですが、 慣れてきたら逆に型宣言が鬱陶しくなってしまいました。(個人開発のレベルでは、ですが)

ダックタイピングとは、

"If it walks like a duck and quacks like a duck, it must be a duck"
(もしもそれがアヒルのように歩き、アヒルのように鳴くのなら、それはアヒルである)
Wikipedia

で表現されるように、「このメソッド持ってるならもうそれってことでオッケー」っていう考え方で、 作っているプログラムの仕様を把握できている限り、非常に柔軟な書き方ができます。

オブジェクト指向ポリモーフィズムを超ラフにした感じでしょうか。

たぶん大規模開発の現場で多用すると、辛くなります。。

pythonの嫌いなところ

たった1年半しかpythonに触れていない初学者の意見ですので、 事実と違う、とか、これはこうすれば/こう考えればいい!とかあったらご指摘ください。

グローバルな組み込み関数がある

len(), max(), min(), sort(),,,

最初は、今時なんなの?と思いましたが慣れると普通に受け入れていて、でも一周回ってやっぱりなんなの?と思い始めました。
というのも、クラス定義するとき、グローバル関数に引数として入ったときの処理を書くにはアンダースコア付きのメソッド(特殊メソッド)を定義するので、 だったら最初からメソッド定義すればいいじゃん・・って思ったからです。

これ、何か深いpythonicな意味があるのかと思っていたら、どうやら過去の遺産みたいなんですよね。
デザインと歴史 FAQ — Python 3.6.5 ドキュメント

主な理由は歴史です。

そうですか。。。

と思ったら英語原文だとちょっと違う文章でした。

Design and History FAQ — Python 3.7.2 documentation

前置のほうが後置よりわかりやすいじゃん、っていうのと、メソッドより関数のほうが引数と戻り値の型が推測しやすいじゃん、ということのようですが、 要は好みの問題っぽいですね。
pythonが読みやすさを重視していること、数学系分野に強いこと、もともと変数に型を定義しない文化であること、を考えると、全うな意見なのかもしれません。

変数に型を強制する方法がない

型推論の言語でも、型を強制したい場面は結構あると思いますが、pythonはそれがありません。
3.5からtypingモジュールとして型ヒントが仕様に追加されましたが、コメントの拡張的な位置づけで、 実行時エラーになったりはしません。

pythonは強い型付けの言語なので、以下のように異なる型の変数で演算するとエラーにしてくれますので、 変数の定義後であればある程度安全ですが(とはいえ実行時エラーにしかなりませんが)、 関数の引数などは型チェックを毎回自前でするのもつらみがあります。

a = 3
b = "10"
print(a+b) # TypeError: unsupported operand type(s) for +: 'int' and 'str'

型の扱いに関しては発展途上のようで、静的型チェックツール(mypy)が開発されたり、 そもそも型チェックを入れることで開発速度が落ちる弊害もあるので一概に善とは言えない、など、 これからpythonとしてはどうなっていくか楽しみな部分です。

型ヒントとその周辺の話題について、以下の記事が参考になりました。

qiita.com

複数行(=文)を無名関数として定義する方法がない

無名関数といえばlambda式ですが、受け付けるのは「式」のみで、「文」は書けません*1

なぜこの仕様が嫌いかというと、pythonをツール的に使うとき、使い捨てコードとしてメソッドチェーンで処理を書きたいときがあって、 それが実現できないからです。

# やりたいことのイメージ(動かない)
some_func(filename).some_method(lambda x:
    # 何らかの処理
    # 何らかの処理
    # 何らかの処理
).some_method(lambda x, y:
    # 何らかの処理
    # 何らかの処理
    # 何らかの処理
)

javascriptとかでは頻繁にみる形式ですが、pythonではコードブロックがインデントであることも相まっていい具合に書く方法がありません。

やはりdefで別途定義して引数に入れるか、デコレータを駆使して何かするか、ということになると思います。 The Zen of Python からしても、上記の形式はきっと「読みにくい」「複数通りの書き方が生まれてしまう」ってことになるんだと思います。

その他、pythonについて知ったこと

好きでも嫌いでもなく、知ったことについてもまとめておきます。

処理が遅い?

競技プログラミングとかしない限り実感するほど遅くはならないと思いますが、やっぱり遅いには遅いです。

本当に速さが必要ならCで書かれたライブラリ(numpy等々)使うか、自分でCで書いて作るのがいいようです。
Cと親和性が高いのはpythonのいいところかと思います。

ちなみに、実行時にバイナリコンパイルされたpycファイルが作られるので、何かしら最適化できるのかと思ったら、現状は処理速度にはほとんど寄与しないようです。

1. Command line and environment — Python 3.7.2 documentation

-O
Remove assert statements and any code conditional on the value of __debug__. Augment the filename for compiled (bytecode) files by adding .opt-1 before the .pyc extension (see PEP 488). See also PYTHONOPTIMIZE.

-OO  
Do -O and also discard docstrings. Augment the filename for compiled (bytecode) files by adding .opt-2 before the .pyc extension (see PEP 488).

おわりに

pythonはどちらかというと好きですが、もうちょっと遊び要素というか、柔軟性も欲しいなって思うことがあります。
Rubyとかは遊び心満載だと聞きますし、かじってみたい気もします。
最近flutterで話題再燃しているDartも、サンプルコードを見るとしっくりくる感じなので、気になっています。

来年はまた一つなにか言語習得したいです。