Last Updated: 2/6/2024, 5:44:57 AM

# リストの数と文字列の長さを数える

len 関数は、 リストが持っている要素の「個数」を数えたり、 文字列の「長さ」を測ります。 使い方は len(変数) と書きます。

#
# >>> 対話モードに
# コピペで実行できまs。
#

# リスト list
len([0, 1, 2])

# 文字列 str
len('abc')
>>> len([0, 1, 2])
3
>>> len('abc')
3
>>>

もちろん他にも様々なオブジェクトの個数を数えたり、あるいは長さを測ることができます。 特に覚える必要はないのですが、ざっくりと眺めていただければと思います。 結果は全て 3 になります。

#
# >>> 対話モードに
# コピペで実行できます。
#

#
# 1) 要素の個数
#

# リスト list
len([0, 1, 2])

# タプル tuple
len((0, 1, 2,))                 

# 集合 set
len({'a', 'b', 'c'})            

# 凍結集合 frozenset
len(frozenset({'a', 'b', 'c'})) 

# 辞書 dict
len({'a': 0, 'b': 1, 'c': 2})   

# 領域 range
len(range(3))                   


#
# 2) 文字列の長さ
#

# 文字列 str
len('abc')

# バイト bytes
len('abc'.encode('utf-8'))

# バイト配列 bytearray
len(bytearray('abc'.encode('utf-8')))

# メモリビュー memoryview
len(memoryview(bytearray('abc'.encode('utf-8'))))

いくつかよくわからない組み込み型が登場しました。 例えば frozenset, bytes, bytearray, memoryview です。 後述します。しかし、別に知らなくても大丈夫です。 ちなみに import しなくても使える型を「組み込み型」と言います。

len 関数はいろんなオブジェクトに対応してそうです。 でも、ちょっと混乱してきました。 len 関数は何を意図しているのでしょうか。 要素の個数を数えているのでしょうか? それとも長さを測っているのでしょうか?

# 1. len の考え方

要素数をかぞえています。

はじめて len を見たとき、いまいちこれが何を意味しているかわかりませんでした。 たしかに文字列のときは  文字列の長さ  なのですが、辞書やリストのときは  オブジェクトの数  だったりします。

もちろん公式ドキュメントによると len 返り値は「長さ(要素の数)」と書かれているので、 「長さ(要素の数)」が、正しいのだと思います。

len(s) (opens new window)
オブジェクトの長さ (要素の数) を返します。 引数は シーケンス (文字列、バイト列、タプル、リスト、range 等) か コレクション (辞書、集合、凍結集合等) です。

len は「要素の数」を数えていると決め打ちした方が、 もしかしたら理解しやすいかなと 感じました 。 ここからは、数をかぞえていそうだなという雰囲気を見ていきたいと思います。

# 1.1. len 関数を引数に取ることができるオブジェクト

以下は len 関数を引数に取ることができるオブジェクトの一覧です。 文字列 str, リスト list 以外にも様々なオブジェクトの長さを測ることができます。

#
# 1. 個数
#
<class 'dict'>
<class 'set'>
<class 'frozenset'>
<class 'tuple'>
<class 'list'>
<class 'range'>

#
# 2. 長さ
#
<class 'str'>
<class 'bytes'>
<class 'bytearray'>
<class 'memoryview'>

# 1.2. どうやって一覧を取り出したの?

以下のコードを対話モード >>> にコピペすればサクッ取り出せます。

#
# >>> 対話モードに
# コピペで実行できます。
#

# 0. Sized
#    __len__ メソッドを持つクラスまたはインスタンスを
#    Sized と表現するらしいです。
#    class collections.abc.Sized
#    https://docs.python.org/ja/3/library/collections.abc.html#collections.abc.Sized
builtin_sized_types = []

# 1. __builtins__
#    組み込みスコープ builtin scope の名前空間です。
for val in __builtins__.__dict__.values():
    # 2. isinstance 関数
    #    オブジェクトがクラスに属するか判定します。
    if isinstance(val, type):
        # 3. hasattr 関数
        #    オブジェクトが属性を持つか判定します。
        if hasattr(val, '__len__'):
            builtin_sized_types.append(val)

# あとは表示するだけ
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'>
>>>

len 関数は、実際には __len__ メソッドを呼び出しているだけです。

#
# 対話モード >>> に
# コピペで実行できます。
#

lst = [0, 1, 2]

len(lst)
# 3

lst.__len()__
# 3

len 関数は、実際には __len__ メソッドを呼び出しているだけなので、 __len__ メソッドを定義してあげれば、ユーザ定義クラスでも len を使うことができます。

# 対話モード >>> に
# コピペで実行できます。
class Cls:
    def __len__(self):
        return 1

len(Cls())  # 1

__len__ メソッドを持つ「組み込み型」を取得できれば、 len 関数が引数に取ることができるクラスの一覧を取り出せます。 「組み込み型」は 「built-in スコープ」 にはいっているので、 これを利用しました。

こうやって len を自分で定義できたりもします。 定義の仕方によっては必ずしも「個数」ではない len 関数も作れてしまいます。

len は int しか受け付けないので、身長 183.5cm みたいな連続数が返すことはできません。 その点を踏まえても「個数」という判断でいいんじゃないかなと思ったりもします。

# 1.3. frozenset

frozenset は変更できない辞書です。 変更できないので add しようとしたりすると AttributeError を投げ返されます。

s = {0, 1, 2}
s.add(4)
s
fs = frozenset({0, 1, 2})
fs.add(4)  # <--- AttributeError
fs
>>> fs.add(4)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'frozenset' object has no attribute 'add'
>>>

# 1.4. range

len(range(3)) は個数を数えているのでしょうか? range は番号を添えると、要素を1つずつ取り出すことができます。

r = range(3)
r[0]
r[1]
r[2]
>>> r = range(3)
>>> r[0]
0
>>> r[1]
1
>>> r[2]
2
>>>

このように番号を添えると要素を見ることができるオブジェクトを「シーケンス」と言ったりします。 例えばリストやタプルもシーケンスに該当します。

基本的なシーケンス型は 3 つあります: リスト、タプル、range オブジェクトです。
シーケンス型 - Python 標準ライブラリ (opens new window)

シーケンスの正確な定義は、以下を参照してください。 ざっくり数字で参照できるもの lst[0] はシーケンスと考えておけばいいと思います。

カスタムのシーケンス型にこれらの演算を完全に実装するのが簡単になるように、 collections.abc.Sequence (opens new window) ABC が提供されています。

# 1.5. str

str もシーケンスです。文字を1つずつ取り出すことができます。 1文字、1文字の集まりと見ていいかなと思っています。

s = 'こんにちわ'
s[0]
s[1]
s[2]
>>> s = 'こんにちわ'
>>> s[0]
'こ'
>>> s[1]
'ん'
>>> s[2]
'に'
>>> 

# 1.6. bytes, bytearray, memoryview

問題は、この人たちです。

実際には、この人たちもシーケンスです。 要素には 0 から 255int が入ります。 構造自体は、そこまで複雑ではありません。

s = 'こんにちは'
s
type(s)  # <class 'str'>
s[0]     # 'こ'
len(s)   # 5

b = str.encode(s)
b
type(b)  # <class 'bytes'>
b[0]     # 227
b[1]     # 129
b[2]     # 147
len(b)   # 15

a = bytearray(b)
a
type(a)  # <class 'bytearray'>
a[0]     # 227
a[1]     # 129
a[2]     # 147
len(a)   # 15

m = memoryview(a)
m
type(m)  # <class 'memoryview'>
m[0]     # 227
m[1]     # 129
m[2]     # 147
len(m)   # 15

bytes, bytearray, memoryview については、以下にまとめました。 知らなくても大丈夫です。 これら3つがシーケンスで個数が数えられるということだけ押さえておいてください。

# ◯ まとめ

色々な考え方、見方はあると思います。 ただ組み込み型については len はオブジェクトの個数を表していると考えると、気持ち的にスッキリします。

まず len は自分でも定義できますが float 型のような連続数は返せず int 型しか返せません。 また 一見して長さを返していそうだった str は文字の集まりで、 bytes, bytearray, memoryview は整数の集まりでした。 そして str, bytes, bytearray, memoryview は シーケンスでした。

len とかじゃなくて count を略して cnt とかでもよかったんじゃないかなと思ったりしました。 例えば Sized (opens new window) ではなくて Countable の方が意味が明瞭になるような気がします。

# 2. メソッドではなく関数である理由

メソッドよりも関数が
読みやすいからです。

メソッドよりも関数が読みやすいからです。 Guido は次の2つの理由を挙げています。 まず第一に接頭辞よりも接尾辞の方がわかりやすいから。 また第二に len という処理に対してインターフェイスを統一することができるから。

ちなみに Go 言語でも同様の理由で、len がメソッドではなく関数で定義されています。 以下の記事は Go 言語に関する記事です。

我々はこの問題を議論しましたが、 len とその類を関数として実装することにしたのは、 実際その方が優れていることと、  基本的な型が実装しているインタフェースを複雑にしないため  です。
lenが、メソッドでなく関数である理由は? - golang.jp (opens new window)

実際 __len__ を定義して int 以外の型を返そうとすると、例外を投げ返されます。 こうやってインターフェイスが統一されているんですね。

class Cls:
    def __len__(self):
        return 'Hello, world!'

len(Cls())  # TypeError
>>> len(Cls())  # 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object cannot be interpreted as an integer
>>> 

このあと、まず疑問を提示し、次に、それに答える形で Guido のメールを引用します。 そして Guido のメールに解説を加えていきます。

# 3. 関数とメソッドを比較する。

# 1. 関数

Python では __len__ メソッドを定義すると、 そのクラスのインスタンスは len 関数で使えるようになります。

class Cls:
    def __len__(self):
        return 1

obj = Cls()
len(obj)  # 1

# 2. メソッド

でも、最初から len メソッド定義すればよくない? と思ってしまうわけです。

class Cls:
    def len(self):
        return 1

obj = Cls()
obj.len()  # 1

min, max が関数として定義されているのは、わかります。 max, min 関数は iterable なオブジェクトを引数に取ります。 for 文で繰り返すことができるオブジェクトを iterable (opens new window) と言います。 例えば、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 が、 メソッドを定義して組み込み関数 (opens new window) を呼び出すように定められています。

なぜ、メソッド呼び出しではなく、 無理やり関数呼び出しをするように設計されているのでしょうか?

>>> 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のオブジェクト指向っぽくないところ (opens new window)
Pythonには関数がある。便利でよく使う関数(標準出力に文字を出力する print() 関数や、配列などの長さを求めるlen() 関数など)は 組み込みではじめから用意されている。関数は何かしらのオブジェクトに関連づけられたメソッドではないため、オブジェクト指向ではないところだ。

# 4. Guido のメール

Guido がそれについてメーリングリストの中で説明しているので、和訳したいと思います。
[Python-3000] Special methods and interface-based type system (opens new window)

特殊メソッドとインターフェースベースの型システム (opens new window)
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:

(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.

(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.

私が説明すると約束した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.

# 5. Guido のメールの解説

# 疑問: どうしてメソッドよりも関数なの?

メソッドよりも関数が読みやすいから

[Python-3000] Special methods and interface-based type system (opens new window)

んー、私はあなたの主張に(特殊メソッドを __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 にメソッドを使う機能と関数を使う機能があるのはなぜですか? (opens new window)

理由 1: 接頭辞は、接尾辞よりも読みやすいから。
理由 2: メソッドは、継承の有無を確認しないといけないから。

# 理由 1: 接頭辞は、接尾辞よりも読みやすいから。

正確には、接頭辞は接尾辞よりも読みやすいから。 この文章は、メソッド(後置記法)は、読みにくい。関数(前置記法)は、読みやすいと言っているのかな。 そうだとしたら、結構、恐ろしい文章。

[Python-3000] Special methods and interface-based type system (opens new window)
(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:
    def len(self):
        """return length of object."""
        raise NotImplementedError

# Len を継承していなかった..
class C:
    def len(self):
        return 'Hello, world!'

obj = C()
obj.len()
# Hello, world!  <- 長さ int じゃなくて文字列 str が返ってきた!

[Python-3000] Special methods and interface-based type system (opens new window)
(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 (opens new window)
私が説明すると約束した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 なのかわからないから。

# 6. この書き方を真似するべきか?

答え: たぶん違うと思います。

ディスパッチテーブルという言葉があります。簡単に抑えておいていただけると幸いです。
ディスパッチテーブル - 新人プログラマに知ってもらいたいメソッドを読みやすく維持するいくつかの原則 Qiita (opens new window)

型によって処理を切り替えることについて考えてみたいと思います。

# 1. 単一ディスパッチ - メソッドによる

一番簡単なのは、ダックタイピングを使ってしまうことです。 自分はこれしか使ったことがありません。

class ClsA:
  def opr(self):
    return 0

class ClsB:
  def opr(self):
    return 1

lst = [ClsA(), ClsB()]

for obj in lst:
    # ClsA, ClsB ともに opr で呼び出せる
    print(obj.opr())
... 
0
1
>>> 

# 2. 単一ディスパッチ - メソッドから関数による

len を真似て関数としてインターフェイスを定義すると読みやすくなると思います。 しかし、よほどプログラム全体を通して汎用的な処理でない限り、ないのではないかなと思います。

class ClsA:
  def _opr(self):
    return 0

class ClsB:
  def _opr(self):
    return 1

def opr(obj):
    return obj._opr()

lst = [ClsA(), ClsB()]

for obj in lst:
    # ClsA, ClsB ともに opr で呼び出せる
    print(opr(obj))
... 
0
1
>>> 

# 3. 単一ディスパッチ - 関数による

Python 3.4 で標準ライブラリ functools (opens new window) に singledispatch という関数が追加されました。 len 関数と同じような感覚でクラスごとに動作が異なる関数を定義できます。

# 対話モード >>> に
# コピペで実行できます。
from functools import singledispatch

class ClsA:
    pass

class ClsB:
    pass

@singledispatch
def opr(arg):
    raise NotImplementedError

@opr.register(ClsA)
def _(arg):
    return 0

@opr.register(ClsB)
def _(arg):
    return 1

lst = [ClsA(), ClsB()]

for obj in lst:
    print(opr(obj))
... 
0
1
>>> 

何が嬉しいんだって感じですが上で見た 1, 2 のような オブジェクト指向ライクの単一ディスパッチではなく、 関数型言語ライクな書き方で単一ディスパッチがしたかったらしいです。

# 4. 多重ディスパッチ - 関数による

Guido は、len とは別のやり方で、関数でも多態性、多重ディスパッチ (opens new window)が実現できる方法を、ごく簡単ではありますが検討しています。 当時は、必要ではないという結論に達していたようです。 以下は Guido の昔のブログです。

Five-minute Multimethods in Python (opens new window)
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.

# 7. 終わりに

ここまで以下のように見てきました。

計算機資源が潤沢になるにつれて、関数型言語が評価されるようになりました。 理解はできていないのですが、クラス、メソッドなんか不要なんやという言説を見た時は、目からウロコでした。

古いJavaのような、クラスにしかメソッドが所属できないモジュールシステムばかりの時代じゃありません。 クラスは基本的に不要だと思います
オブジェクト指向の呪いと、その避け方 - mizch's blog
アンチパターン: 特に理由もないクラスメソッドへの所属
ttps://mizchi.hatenablog.com/entry/2018/07/31/124354

メソッドよりも関数の方が読みやすい。関数で書けるのであれば、 オブジェクト指向である必要がないならば、関数で記述した方が望ましい気がします。 またよく使う処理については、組み込み関数として len を用意しておいた方が読みやすいと思います。

こういった洞察を持っているのが Guido の凄さだと思います。 関数は読みやすい、動的言語で関数でも多態性も実装したい、という2つの思いがあると、 len のような実装された方が導き出されるのかなと思います。

ただ、オブジェクト指向という枠の中で、組み上がってしまっているなかで、 例外的に関数を設けてしまったのは、どうしても一貫性を損ねる結果となってしまったかなと思います。 英語の文章、Guido のメールは昔の Python の Q&A からの引用になります。 いまは消されました。代わりにいまは次のような文章が掲載されています。

主な理由は歴史です(昔からそうやって実装されていたということ)。 ... 個々のケースについては粗探しのしようがありますが、 Python の一部であるし、根本的な変更をするには遅すぎます。 これらの関数は、大規模なコードの破壊を避けるために残す必要があります。

やはり、個人的には len が関数として定義されてしまったのは厳しいのではないかなと感じたりもします。 len, size, length とメソッド名がバラバラになる可能性はありますが、 コーディング規約レベルで PEP 8 のレベルで、統一しておくのがよかったのかなと思ったり、思わなかったりします。

よく 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 (opens new window)