Python の len の意味、なんでメソッドではなく関数なの?
Python では、もし汎用的な処理でかつ、オブジェクト指向である必要がないならば、メソッドよりも関数の方が望ましいという雰囲気、感覚を感じたりもします。
len の意味
はじめて len を見たとき、いまいちこれが何を意味しているかわかりませんでした。 たしかに文字列のときは文字列の長さなのですが、辞書やリストのときは要素数だったりします。 どちらかというと要素数の方が使われ方としては多い気がします。
# len が使える組み込み型を取り出して見ます。 # このコードの解説は、いつかします。 builtin_types = (cls for cls in __builtins__.__dict__.values() if isinstance(cls, type)) builtin_sized_types = (cls for cls in builtin_types if hasattr(cls, '__len__')) print(*builtin_sized_types, sep='\n')
>>> print(*builtin_sized_types, sep='\n') <class 'memoryview'> <class 'bytearray'> <class 'bytes'> <class 'dict'> <class 'frozenset'> <class 'list'> <class 'range'> <class 'set'> <class 'str'> <class 'tuple'> >>>
これを分けると
# 個数 <class 'dict'> <class 'set'> <class 'frozenset'> <class 'tuple'> <class 'list'> <class 'range'> # 長さ <class 'str'> <class 'bytes'> <class 'bytearray'> # よく分からない。今回は保留します。 <class 'memoryview'>
実際のところ str, bytes, bytearray も各要素にオブジェクトを保存しているので、
オブジェクトの数という理解でいいかなと思ったりもします。
str は Unicode のコードポイントの集まりで len はコードポイントの個数を数えています。 bytes は数字の集まりで len は数字の個数を数えています。 bytearray はミュータブルな bytes です、こちらも同じく len は数字の個数を数えています。
実際、添字表記で1つ1つのオブジェクトを参照することができます。 このようにして添字表記で参照できるオブジェクトをシーケンスと読んだりします。
s = 'こんにちは' s[0] # 'こ' b = s.encode('utf-8') b[0] # 227 a = bytearray(b) a[0] # 227
上のコードを見ると 'こんにちは' を utf-8 に変換しているのに utf-8 じゃなくてなんで数字なの?みたいな疑問もあるかと思います。
str, bytes, bytearray の違いについては、また別に機会にご紹介させてください。
再度整理すると次のような感じになります。
# 個数 <class 'dict'> <class 'set'> <class 'frozenset'> <class 'tuple'> <class 'list'> <class 'range'> <class 'str'> <class 'bytes'> <class 'bytearray'> # よく分からない。今回は保留します。 <class 'memoryview'>
色々な考え方、見方はありますが len はオブジェクトの個数を表していると考えると、気持ち的にスッキリします。
長い前置き失礼しました。本題に入ります。
比較: 関数とメソッド
1. 関数
Python では __len__ メソッドを定義すると
len 関数で使えるようになります。
class C(object): def __len__(self): return 1 obj = C() len(obj) # 1
2. メソッド
でも、最初から len メソッド定義すればよくない?
と思ってしまうわけです。
class C(object): def len(self): return 1 obj = C() obj.len() # 1
min, max が関数として定義されているのは、わかります。max, min 関数は iterable なオブジェクトを引数に取ります。for 文で繰り返すことができるオブジェクトを iterable なオブジェクトと言います。例えば、range, list, tuple, dict などが該当します。
for element in iterable: print(element)
たとえ、そのオブジェクトがどのクラスであったとしても、iterable であるならば処理は同じです。クラスごとにメソッドを定義するのはどう考えても冗長ですから、処理を関数で一箇所にまとめるのが妥当でしょう。
でも len は違います。len の実装はクラスごとに異なります。1つの関数にまとめることはできないはずです。それを無理やり各クラスごとに __len__ メソッドを定義して、len 関数で呼び出しています。
繰り返しになりますが len 関数は __len__ メソッドを呼び出ししているだけです。それなら上のような実装でよくないのかなとも思ってしまうのです。
このような実装は2つの欠点があると思います。まず、第一にメソッドで書いたり、関数で書いたりして、一貫性が損なわれているような気もします。 また、第二に無理やりメソッドではなく関数で呼び出すような設計をしていて。ひどく二度手間のように感じます。
しかも、Python では len の他にも abs, bool, str, repr, next, iter が、メソッドを定義して組み込み関数 を呼び出すように定められています。
なぜ、メソッド呼び出しではなく、無理やり関数呼び出しをするように設計されているのでしょうか?
>>> lst = [0, 1, 2, 3] >>> >>> # 関数 ... 実装されている。 >>> len(lst) 4 >>> >>> # メソッド ... 実装されていない。 >>> lst.len() Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'list' object has no attribute 'len' >>>
Pythonのオブジェクト指向っぽくないところ
Pythonには関数がある。便利でよく使う関数(標準出力に文字を出力するprint()
関数や、配列などの長さを求めるlen()
関数など)は組み込みではじめから用意されている。関数は何かしらのオブジェクトに関連づけられたメソッドではないため、オブジェクト指向ではないところだ。
Guido がそれについてメーリングリストの中で説明しているので、和訳したいと思います。
[Python-3000] Special methods and interface-based type system
疑問: どうしてメソッドよりも関数なの?
[Python-3000] Special methods and interface-based type system
んー、私はあなたの主張に(特殊メソッドを __special__ ではなく special として定義するべきだという主張に)同意できるかな..(ちょっと考えてみますね)。
Hm. I'm not sure I agree (figure that :-).2つのちょっとした Python の設計背景があります。まず、それについて説明しましょう。
There are two bits of "Python rationale" that I'd like to explain first.まず第一に私は Human Computer Interaction, HCI の観点から x.len() よりも len(x) を選びました。 (obj.__len__() メソッドを定義して len(obj) 関数の動作を定義するのは、だいぶ後になってから実装されました。) 実際には2つの理由があります、共に HCI によるものです。
First of all, I chose len(x) over x.len() for HCI reasons (def __len__() came much later). There are two intertwined reasons actually, both HCI:
昔は len 関数の中に、各クラスに対するオブジェクトの長さを計測する処理が、まとめて定義されていました。
いまでは各クラスの __len__ メソッドの中に、それぞれ定義されています。
複数の型に対しての総称的な操作で、対象のオブジェクトがメソッドを全く持っていなかった (例えば、タプル) としても働くよう意図したものに関数は使われました。
Python にメソッドを使う機能と関数を使う機能があるのはなぜですか?
理由 1: 接頭辞は、接尾辞よりも読みやすいから。
理由 2: メソッドは、継承の有無を確認しないといけないから。
理由 1: 接頭辞は、接尾辞よりも読みやすいから。
正確には、接頭辞は接尾辞よりも読みやすいから。 この文章は、メソッド(後置記法)は、読みにくい。関数(前置記法)は、読みやすいと言っているのかな。そうだとしたら、結構、恐ろしい文章。
[Python-3000] Special methods and interface-based type system
(a) いくつかの演算については、前置記法は後置記法よりも読みやすい ー 前置記法(そして中置記法!)の演算は、数学の中で長い伝統を持っている。前置記法と中置記法の演算は、表記法に適していて、数学者が問題について考えやすくする。
(a) For some operations, prefix notation just reads better than postfix — prefix (and infix!) operations have a long tradition in mathematics which likes notations where the visuals help the mathematician thinking about a problem.x*(a+b) のような式を x*a + x*b に書き換える容易さと、生のオブジェクト指向の表記を使って同じことをする難しさを比べてみてください。
Compare the ease with which we rewrite a formula like x*(a+b) into x*a + x*b to the clumsiness of doing the same thing using a raw OO notation.
書き換えやすさについて検討。メソッドは、後置記法というよりも、中置記法っぽくなってしまっていますが。
ただ、どちらが書き換えやすいか、と言われると、たしかに関数の方が、書き換えやすい。それに関数の方が読みやすい。
from operator import add from operator import mul def sample_code(): # 書き換えやすい x, a, b = 3, 4, 5 assert mul(x, add(a, b)) == add(mul(x, a), mul(x, b)) # 書き換えにくい x, a, b = map(Int, (x, a, b)) assert x.mul(a.add(b)) == x.mul(a).add(x.mul(b)) class Int(int): def mul(self, other): return type(self)(self * other) def add(self, other): return type(self)(self + other) sample_code()
理由 2: メソッドは、継承の有無を確認しないといけないから。
関数で呼び出す場合、組み込み関数として len が規定されていれば、プログラムを読む側は len という関数名だけで、それがオブジェクトの長さを返す関数だと判別できます。
# オブジェクトの長さが返ってくる。 len(obj)
反対に、メソッドで呼び出す場合、オブジェクトの長さを返すメソッド len が抽象メソッドを持つ Len クラス が規定されていたとして規定されていたとしても、それがオブジェクトの長さを返す関数だと判別できません。
# 何が返ってくるか、わからない。 obj.len()
するとある obj.len がメソッドが、抽象メソッドを len を実装したものなのか、あるいは別の用途で実装されたメソッドなのか、obj.len を見ただけでは判別がつかないからです。結局、ちゃんと確認しようと思ったら obj.len メソッドのコードを読まないといけなくなります。
class Len(object): def len(self): """return length of object.""" raise NotImplementedError # Len を継承していなかった.. class C(object): def len(self): return 'Hello, world!' obj = C()
[Python-3000] Special methods and interface-based type system
(b) 私が len(x) とコードに書かれているのを読んだときは、何かの長さを問い合わせているのだろうというのはわかります。このことは2つのことを示しています。結果は整数であること、引数は何らかのコンテナのであること。
When I read code that says len(x) I know that it is asking for the length of something. This tells me two things: the result is an integer, and the argument is some kind of container.反対に、x.len() を読んだときは、x がインターフェイスを実装しているか、または標準的な len メソッドを持ったクラスから継承した何らかのコンテナであることを事前に知っておかなければならない。
To the contrary, when I read x.len(), I have to already know that x is some kind of container implementing an interface or inheriting from a class that has a standard len().mapping を実装していないクラスが get, keys メソッドを持っていたとき、またはファイルではない何がしかのクラスが write メソッドを持っていたときに、私たちがよく戸惑うのを、同じように経験してみてください(どういう意味なんだろう... おそらく、継承さえしていないような場合は、個々のメソッドの意味を類推することが、より難しくなる、とうことでしょうか。)。
Witness the confusion we occasionally have when a class that is not implementing a mapping has a get() or keys() method, or something that isn’t a file has a write() method.
こんなことし出したら組み込み関数だらけになってしまうんじゃ無いかなとも思ったのですが、
よく使うものについてだけ、組み込み関数として採用したという理解でいいのでしょうか。
確かに len(obj) と書かれているから、あーオブジェクトの数かと思ってさらっと流しています。 しかし、これがもし obj.len() だと、さらっと流せないかもしれない気がします。
疑問: なんで len メソッドじゃなくて
__len__ メソッドで定義するの?
答え: 一般的な語を使いたくないから。
len メソッドを定義して、len 関数で呼び出す。これでいいんじゃないの?って思うわけです。
class C(object): def len(self): return 1 obj = C() len(obj) # 1
しかし、クラスは継承されることを前提にしているので、メソッドに一般的な名前を使いたくなかったらしいです。
[Python-3000] Special methods and interface-based type system
私が説明すると約束した2つ目の Python の設計背景は、なぜ特殊メソッドの見た目を単純に special とはせずに __special__ したのかだ。
The second bit of Python rationale I promised to explain is the reason why I chose special methods to look __special__ and not merely special.私は、多くのクラスがメソッドをオーバーライドするだろうと考えた。例えば、一般的なメソッド名(例えば __add__ や __getitem__)、あまり一般的でないメソッド名(例えば pickle の __reduce__。これは、長いこと C 言語で一切サポートされていませんでした。)
I was anticipating lots of operations that classes might want to override, some standard (e.g. __add__ or __getitem__), some not so standard (e.g. pickle‘s __reduce__ for a long time had no support in C code at all).私はこれらの特殊なメソッドには、一般的なメソッド名を使って欲しくなかった。なぜなら、すでに設計されたクラス、またはこれらの全ての特殊メソッドを覚えていないユーザによって書かれたクラスが、意図せずメソッドをオーバーライドしてしやすく、結果として悲劇的な結果を引き起こす可能性を秘めているからだ("すでに設計されたクラス" というのは、特殊メソッドを追加した時に、古いコードで同じ特殊メソッド名が既に使われてしまうと、後方互換性が失われることを指しているのかな..)。
I didn’t want these special operations to use ordinary method names, because then pre-existing classes, or classes written by users without an encyclopedic memory for all the special methods, would be liable to accidentally define operations they didn’t mean to implement, with possibly disastrous consequences.
それに len っていうメソッドがあったら、わざわざ関数呼び出さないで、そのままメソッド使ってしまいそうですしね笑 __len__ メソッドにして len 関数を使ってもらうってのが、確かに良さそうですね。
◯ obj.next メソッドから obj.__next__ メソッドに
Guido は、このことをよく気にします。 イテレータでも Python 2 では obj.next メソッドを定義して next 関数で呼び出す方法から、Python 3 では len と同じように obj.__next__ メソッドを定義して, next(obj) から呼び出す方法に移行しています。
# Python 2 class Iterator(object): def next(self): ...
# Python 3 class Iterator(object): def __next__(self): ...
このように変更した理由は len と同じで next メソッドでは、イテレータの next なのかユーザが何か別の目的で作った next なのかわからないから。
なんで iterator には自分自身を返すメソッド __iter__ を実装するの?
PEP 3114 -- Renaming iterator.next() to iterator.__next__()
この書き方を真似するべきか?
答え: たぶん違うと思います。
ディスパッチテーブルという言葉があります。簡単に抑えておいていただけると幸いです。
ディスパッチテーブル - 新人プログラマに知ってもらいたいメソッドを読みやすく維持するいくつかの原則 Qiita
1. 単一ディスパッチ - メソッドによる
このように len を真似て書くことは、 よほどプログラム全体を通して汎用的な処理でない限り、ないのではないかなと思います。
class C(object): def _opr(self): return None def opr(obj): return obj._opr() obj = C() opr(obj)
2. 単一ディスパッチ - 関数による
Python 3.4 で標準ライブラリ functools に singledispatch という関数が追加されました。 len 関数と同じような感覚でクラスごとに動作が異なる関数を定義できます。
- Python 3.4.0 の新機能 (3) - Single-dispatch generic functions
- PEP 443 -- Single-dispatch generic functions
3. 多重ディスパッチ - 関数による
Guido は、len とは別のやり方で、関数でも多態性、多重ディスパッチが実現できる方法を、ごく簡単ではありますが検討しています。 当時は、必要ではないという結論に達していたようです。
I used to believe that multimethods were so advanced I would never need them. Well, maybe I still believe that, but here's a quick and dirty implementation of multimethods so you can see for yourself. Some assembly required; advanced functionality left as an exercise for the reader.
All Things Pythonic Five-minute Multimethods in Python
◯ まとめ
計算機資源が潤沢になるにつれて、関数型言語が評価されるようになりました。 理解はできていないのですが、クラス、メソッドなんか不要なんやという言説を見た時は、目からウロコでした。
古いJavaのような、クラスにしかメソッドが所属できないモジュールシステムばかりの時代じゃありません。 クラスは基本的に不要だと思います
オブジェクト指向の呪いと、その避け方 - mizch's blog
アンチパターン: 特に理由もないクラスメソッドへの所属
ttps://mizchi.hatenablog.com/entry/2018/07/31/124354
メソッドよりも関数の方が読みやすい。関数で書けるのであれば、オブジェクト指向である必要がないならば、関数で記述した方が望ましい気がします。
またよく使う処理については、組み込み関数として len を用意しておいた方が読みやすいと思います。
こういった洞察を持っているのが Guido の凄さだと思います。 関数は読みやすい、動的言語で関数でも多態性も実装したい、という2つの思いがあると、len のような実装された方が導き出されるのかなと思います。
ただ、オブジェクト指向という枠の中で、組み上がってしまっているなかで、 例外的に関数を設けてしまったのは、どうしても一貫性を損ねる結果となってしまったかなと思います。 英語の文章は昔の Python の Q&A からの引用になります。 いまは消されました。代わりにいまは次のような文章が掲載されています。
やはり、個人的には len が関数として定義されてしまったのは厳しいのではないかなと感じたりもします。
主な理由は歴史です(昔からそうやって実装されていたということ)。 ... 個々のケースについては粗探しのしようがありますが、 Python の一部であるし、根本的な変更をするには遅すぎます。 これらの関数は、大規模なコードの破壊を避けるために残す必要があります。
よく len の処理について、辛辣に批評したものをよく目にします。
そして、悲しいことに、Guido が配慮したことの 十分の一も考えていないにも関わらず、おそらく自分も気づかないうちによくやっているのだと思います。
ただ、いくらか気をつけないといけないなと感じたりもします。
Guido が BDFL から降りた時の文章を引用します。
いま PEP 572 の仕事は、終わった。私はこれ以上 PEP のために戦いたくないし、多くの人が私が決定したことを侮蔑するのを聞きたくない。
Now that PEP 572 is done, I don't ever want to have to fight so hard for a PEP and find that so many people despise my decisions.
Transfer of power - Guido van Rossum
終わりに
つぎは、関数の方が読みやすいからと無理やり関数にした len とは違い、 読みにくいのに関数ではなくメソッドとして定義された join について見ることによって、 Guido やほかの Python の開発者たちが、何を考えていたのか、理解を深めていきたいと思います。
# (本物) ', '.join(['Hello', 'wolrd!'])
# (偽物)上は読み辛い。join こそ、組込関数にしてほしいのになんで? join(['Hello', 'world'], ', ')