気がつくとPythonは結構な年数使っています。が、言語としては好きではない部類でした。
主流だからチームで使うのが無難とか、機械学習だとこれ一択だから。とかの理由で使ってきたのが正直なところです。
そういう自分が、最近「あれ、Python面白いじゃん。」と思ったのが、本稿のきっかけです。
言語の好み
仕事のツールとして使う分には好みは関係ありません。単に与えられた道具をうまく使うだけです。
というのは嘘ではないですが、建前でもあり、プログラマーというのは理想の言語を求めたがる性があります。でなければ、星の数ほどのプログラミング言語は生まれてきていません。
「理想」というのは軽くいうと単なる好みの問題で、重くいうと思想・哲学の問題になります。
Pythonの第一印象は最悪
端的にいうと、
・インデント(タブ)が文法に含まれるのはありえない
・関数がブロック表記(begin
~end
,{
~}
) で囲まれていないのが気持ち悪い
というところです。
あとは、本稿のテーマですが、「一つのことをするのに一つの表現しか許さない」という話を聞いたからです。
Pythonの哲学
最近知ったのですが、import this
と書くとpythonの哲学が出力されます。
$ python -c "import this"
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
英語原文 | プログラマー向け意訳 |
---|---|
Beautiful is better than ugly. | 美しいコードは、読みにくいコードより優れている。 |
Explicit is better than implicit. | 暗黙的な処理より、明示的な処理の方が優れている。 |
Simple is better than complex. | 複雑なコードより、シンプルなコードの方が優れている。 |
Complex is better than complicated. | 複雑な問題には複雑な解決策が必要な場合もあるが、不必要にこねくり回したコードよりは良い。 |
Flat is better than nested. | ネストが深い構造より、フラットな構造の方が優れている。 |
Sparse is better than dense. | ぎっしり詰まったコードより、適度に空白がある方が読みやすい。 |
Readability counts. | コードの読みやすさは非常に重要である。 |
Special cases aren't special enough to break the rules. | 特殊なケースであっても、基本原則を破るほどの理由にはならない。 |
Although practicality beats purity. | ただし、現実的な実用性は、理想的な純粋さよりも優先されることもある。 |
Errors should never pass silently. | エラーは、黙って無視されるべきではない。 |
Unless explicitly silenced. | 明示的に抑制されている場合を除いては。 |
In the face of ambiguity, refuse the temptation to guess. | 曖昧な状況では、推測に頼る誘惑に抵抗せよ。 |
There should be one-- and preferably only one --obvious way to do it. | 何かを行うには、明白な方法が一つ、できれば一つだけであるべきだ。 |
Although that way may not be obvious at first unless you're Dutch. | ただし、その方法が最初から明白でない場合もある。オランダ人でない限りは1。 |
Now is better than never. | 何もしないよりは、今始める方が良い。 |
Although never is often better than right now. | ただし、準備ができていない「今すぐ」やるよりは、やらない方がマシな場合も多い。 |
If the implementation is hard to explain, it's a bad idea. | 実装方法を説明するのが難しいなら、それは悪い設計である可能性が高い。 |
If the implementation is easy to explain, it may be a good idea. | 実装方法を説明するのが簡単なら、それは良い設計である可能性が高い。 |
Namespaces are one honking great idea -- let's do more of those! | 名前空間はとてつもなく素晴らしいアイデアだ。もっと活用していこう! |
読んでみると、個人的にもこの哲学と意見が違うものは一つもありません。
目指すところは簡潔で明瞭なプログラミング言語というのが伺えます。
が、この表現の結果としてのPythonは極端すぎる。インデントの件からして「ミニマリストが作った言語」という感想です。
"There should be one"
Pythonのこの、
There should be one-- and preferably only one --obvious way to do it.
は端的に「同じことをするのに方法はひとつだけの方がいい」を意味します。この哲学はGo言語も同じで、Rubyと真逆です。
Rubyの有名なスローガンは、
"There's more than one way to do it."(やり方はいくつもある)
です。
両者とも言わんことは理解できて、目指しているところ同じで、プログラマーの生産性の向上だと思います。
チームで仕事をする場合はコードの記述は奇をてらわずシンプルで一貫性があった方が良いのは明確だし、個人でコードを書く場合は表現力が豊かな方がモチベーションが上がります。(まあ、後者は人によるかもしれませんが)
Pythonの表現の多様性
おそらくPythonも歴史的変遷があって、いろいろな機能が追加されてきて、初期のミニマリズムからは変わってきているのかなと思います。
いや、pythonとてThere should be one
とは言っているが、実際のところ「同じことをする」のに多様な表現が可能ですよ。
という例を示したいのが、本稿の本題です。
例題
Codewarsからお題を拝借します。
ある数字が与えられた時に、各桁の数をそれぞれを2乗にしてくっつけた数字を作ってください。
例:
9119 -> 811181 (9-1-1-9 -> 81-1-1-81)
765 -> 493625 (7-6-5 -> 49-36-25)
この問題の解き方は、(1)数字のまま扱う (2)いったん文字列にする、という2つ方針が考えられますが、コード表現の多様性を言いたいので、とりあえず(2)の方針に揃えたいと思います。
Solutionその1
一般的にfor文で簡潔に書くと下記のようになると思います。
def square_digits1(num):
s = ''
for c in str(num):
s += str(int(c)**2)
return int(s)
Solutionその2
Solution 1と同じことをしているが、リスト内包表記を使用したものです。
def square_digits2(num):
return int(''.join([str(int(c)**2) for c in str(num)]))
Solutionその3
Solution 2とほとんど同じように見えますが、for..
文の代わりに、関数型プログラミングの機能map(lambda ...)
を使っています。
注意すべき点は、map()を使った場合は内部動作が異なります。遅延評価されます2。
def square_digits3(num):
return int(''.join(map(lambda c: str(int(c)**2), str(num))))
その他
細かいところだと、実装方法のバリエーションはいろいろあります。
例えば、for文とmapの組み合わせで
def square_digits3_1(num):
s = ''
for c in map(lambda c: int(c)**2, str(num)):
s += str(c)
return int(s)
とか、関数型プログラミングのreduce()関数を使用する、
from functools import reduce
def square_digits4(num):
return int(reduce(lambda s, n: s + str((int(n)**2)), str(num), ''))
とか。
自分はPythonの達人でもなんでもないので、上記以外のバリエーションはもっとあるかもしれません。
まとめ
昔からのPythonウォッチャーではないので、歴史的な変移は知りませんが、おそらくバージョンアップごとに言語機能が追加されて、初期の頃よりもずっと表現のバリエーションが増えたのでしょう。
「Pythonさん、There should be one
とか言っているから、味気ない言語かと思ったら、言っていることとやっていること、ちゃうやんけ!」が、結局、言いたかっただけです。