1 of 54

Pythonはどうやってlen関数で長さを手にいれているの?

2024.11.16

清水川 貴之

1

2 of 54

  • Pythonプログラマー(Python 2.3~)
    • sphinx, sphinx-intl
    • django-redshift-backend

  • Pythonコミュニティー運営
    • Python mini Hack-a-thon
    • Sphinx users JP
    • PyCon JP Association 会計理事

  • (株) BePROUD; IT Architect

  • Books 翻訳/執筆

2

3 of 54

Pythonはどうやってlen関数で長さを手にいれているの?

How does python get length with the len() function?

3

4 of 54

Target attendees

以下のように思っている方

  • Pythonは入門したが、なんだかしっくりこない..

  • なんでlenは関数なんだろう

  • len()関数だなんてPythonはオブジェクト指向じゃないな

4

5 of 54

アジェンダ

  1. len() がオブジェクトの長さを手に入れる方法
    1. なんでPythonはlen()関数なの
    2. Protocol: オブジェクトの振る舞い�
  2. if がオブジェクトのTrue/Falseを判断する方法�
  3. for がオブジェクトの繰り返し要素を取得する方法�
  4. まとめ

  • References

5

6 of 54

len() がオブジェクトの長さを手に入れる方法

初級

7 of 54

len()関数に文字列を渡して起こること

  1. len関数で文字列の長さを得ます

  1. obj.__len__() メソッドで文字列の長さを得ます

  1. 得られる結果は同じ
  2. len(obj)は内部で obj.__len__() を実行しています

7

>>> len("もじれつ")

4

>>> "もじれつ".__len__()

4

8 of 54

len()要らないのでは?

  1. obj.__len__() が呼ばれるなら obj.length() でよかったのでは?

8

"もじれつ"

__len__

4

4

return

9 of 54

len()要らないのでは?

  • obj.__len__() が呼ばれるなら obj.length() でよかったのでは?
    1. len() はもうちょっと仕事してます
    2. __len__() の値がintかチェックしています

9

"もじれつ"

len()

__len__

4

int?

TypeError

4

return

Yes

No

10 of 54

__len__() がint以外の値を返すと..

len関数で、�型と値をチェック!

10

>>> class WaruiObj:

... def __len__(self):

... return 1.2

...

>>> w = WaruiObj()

>>> len(w)

Traceback (most recent call last):

File "<python-input-3>", line 1, in <module>

len(w)

~~~^^^

TypeError: 'float' object cannot be interpreted as an integer

__len__

TypeError

return

Yes

No

int?

len()

sys.maxsize を超える値を返すとOverflowError例外を起こします。

11 of 54

len()関数の役割

  1. 渡されたオブジェクトの __len__() APIを使って、
  2. 取得した値をチェック、変換して、
  3. 呼び出し元に適切な値を返す

11

"もじれつ"

len()

int?

TypeError

Yes

No

__len__

__len__

4

4

return

Adapter Pattern!!

12 of 54

Adapter Pattern とは

12

Adapter Pattern(アダプター・パターン)とは、GoF (Gang of Four; 4人のギャングたち) によって定義されたデザインパターンの1つである。Adapter パターンを用いると、既存のクラスに対して修正を加えることなく、インタフェースを変更することができる。

Interface

交流 100V~240V

直流 20V 3.25A

Protocol

Wikipedia より

13 of 54

どのオブジェクトにも使えるlen() Adapter

  1. len() 関数は、任意のオブジェクトに対してAdapterとして作用します。
  2. len() は、obj.__len__() があれば、どんなオブジェクトにも使えるAdapterです。

13

任意のobject

len()

__len__

TypeError

return

Yes

No

__len__

Interface

Interface

Protocol

プロトコル

異常?

14 of 54

listに len() Adapter

  1. list.__len__() は中に持っている要素数を返します。
  2. 要素数はlist自身が知っています

14

list:

[2, None, ‘Yo’]

len()

__len__

3

int?

TypeError

3

return

Yes

No

__len__

15 of 54

dictに len() Adapter

  1. dict.__len__() は中に持っているキーの数を返します。
  2. キーの数はdict自身が知っています

15

dict: {

"age": 999,

"name": "Hoge"�}

len()

__len__

2

int?

TypeError

2

return

Yes

No

__len__

16 of 54

独自のデータ型に len() Adapter

  1. __len__() メソッドを実装した独自クラスを定義します

16

>>> import random

>>> class Random:

... def __len__(self):

... return random.randint(0, 10)

...

>>> r = Random()

>>> len(r)

10

>>> len(r)

0

>>> len(r)

5

__len__

TypeError

return

Yes

No

int?

len()

?

__len__

独自データ型

17 of 54

Protocol: オブジェクトの振る舞い

  1. len() は、obj.__len__() があれば動作します。
  2. 言い換えると、長さの概念を持つオブジェクトは、obj.__len__() を実装する必要があります。
  3. 長さの「プロトコルを実装する」と言います。

17

任意のobject

len()

__len__

int?

TypeError

return

Yes

No

__len__

Interface

Protocol

18 of 54

Protocol ってどこに書いてあるの?

  1. 実は、Pythonの公式ドキュメントに何度か登場してます

18

__len__

__len__

Protocol

19 of 54

Protocolの定義はどこにあるの?

  1. Protocolの一覧などは(現在も)なさそう…
  2. ドキュメントへの初登場はPython-2.2 (2001年)
  3. PEP-544 (2017年5月)でPEPに初登場
    1. Protocol一覧の定義ではなく、型ヒントのため明確化

19

__len__

__len__

Protocol

PEPはPython拡張提案(Python Enhancement Proposal)を表しています。PEPはPythonのコミュニティに対して情報を提供したり、Pythonの新機能やプロセス、環境などを説明するための設計書です。PEPは、技術的な仕様と、その機能が必要な論理的な理由を提供しなければなりません。

PEP-1 より

20 of 54

Protocol一覧の代わりに

  1. http://docs.python.jp/3/library/collections.abc.html 

20

21 of 54

ここまでのまとめ

len() は Adapter

オブジェクトとAdapterが�通信する規約がプロトコル

21

22 of 54

Adapter、値のチェックしてるだけでしょ?

22

len()

int?

TypeError

Yes

No

__len__

4

4

return

len() 、 max() 、 min() を組み込み関数として実装することで、それぞれの型のメソッドとして実装するより少ないコードで済みます。

こういうメリットもあるよ

23 of 54

次はもうちょっと複雑な例

(´・ω・`)ノ

23

24 of 54

if がオブジェクトのTrue/Falseを判断する方法

初級++

25 of 54

if 文のルール

  1. if文の例

  • 内部では自動的にbool()で変換されます

  • はい、bool() 関数 Adapter です。

25

if obj:

print("Trueだ!")�else:� print("Falseだ!")

if bool(obj):

26 of 54

bool()関数に数値を渡したときに起こること

  1. bool関数で数値の真偽(True/False)を判別

  • obj.__bool__() メソッドでしょ?

  • はい(´・ω・`)
  • じゃあ次は文字列で。

26

>>> bool(123)

True

>>> (123).__bool__()

True

27 of 54

bool()関数に文字を渡したときに起こること

  1. bool関数で文字列の真偽(True/False)を判別

  • obj.__bool__() メソッド

  • あれっ?

27

>>> bool("もじれつ")

True

>>> "もじれつ".__bool__()

Traceback (most recent call last):

File "<python-input-5>", line 1, in <module>

"もじれつ".__bool__()

^^^^^^^^^^^^^^^^^^^

AttributeError: 'str' object has no attribute '__bool__'

28 of 54

数値や文字を bool() に変換するRule

28

偽と見なされる条件

クラスが __bool__() または __len__() メソッドを定義していれば、それらのメソッドが整数 0 または bool 値 False を返すとき。

真と見なされる条件

偽じゃないやつ

真偽値判定 より

bool() は len() よりも仕事してそう

29 of 54

bool() Adapter

  1. __bool__ がない場合は、bool(len(obj)) 相当の処理を行う

29

任意のobject

len()

bool()

Yes

__bool__

return

__len__

メソッドある?

No

Yes

bool型?

No

bool(value)

or

TypeError

30 of 54

独自のデータ型に bool() Adapter

  1. __bool__() メソッドを実装した独自クラスを定義します

  • これは、正の整数ならTrueと判定されるint型です

30

>>> class PositiveInt(int):

... def __bool__(self):

... return self > 0

...

>>> bool(PositiveInt(10))

True

>>> bool(PositiveInt(-3)) # 0以下の値はFalse

False

>>> bool(-3) # 本来のintはマイナス値もTrue

True

31 of 54

さらにレベル上げていくよー

(`・ω・´)

31

32 of 54

for がオブジェクトの�繰り返し要素を取得する方法

初級++++

33 of 54

for 文のルール

  1. for 文の例

  • 内部では自動的にiter()で変換されます

  • はい、iter() 関数 Adapter です。

33

for o in obj:

print(o)

for o in iter(obj):

34 of 54

object を iter() に変換するルール

34

iter(object) は イテレータ (iterator) オブジェクトを返します。object は反復プロトコル (__iter__() メソッド) か、シーケンスプロトコル (引数が 0 から開始する __getitem__() メソッド) をサポートする集合オブジェクトでなければなりません。これらのプロトコルが両方ともサポートされていない場合、 TypeError が送出されます。

bool() よりずっと大変そう

35 of 54

iter() Adapter

35

任意のobject

iter()

Yes

__iter__

return

メソッド�ある?

No

Yes

No

イテレータ?

__getitem__

0から順番に obj.__getitem__()に渡して、IndexErrorが発生するまで繰り返すiterator実装を提供する

iterator

or

Iterator Pattern!!

参考: Wikipedia

TypeError

36 of 54

iter() が返すIteratorとは

36

イテレータ(iterator)は、データの流れを表現するオブジェクトです。イテレータの __next__() メソッドを繰り返し呼び出す (または組み込み関数 next() に渡す) と、流れの中の要素を一つずつ返します。データがなくなると、代わりに StopIteration 例外を送出します。

イテレータオブジェクト自体は以下の 2 つのメソッドをサポートする必要があります。これらのメソッドは 2 つ合わせて iterator protocol: (イテレータプロトコル) を成します … __next__(), __iter__()

37 of 54

for 文のルール(もうちょっと正確に)

  1. for 文の例

  • 内部ではこう解釈されます

37

for o in obj:

print(o)

it = iter(obj)

while True:

try:

o = next(it)

except StopIteration:

break

print(o)

はい、iter() 関数 Adapter と next() 関数 Adapterです。

38 of 54

next() Adapter と iterator

38

iterator

  • 対象オブジェクト(リスト等)
  • 位置カウンタ

対象オブジェクトから位置カウンタを使って値を取り出して返す

iterator自体を返す

next()

return

__next__

__iter__

and

39 of 54

iteratorの実装例

39

class MyIterator:

def __init__(self, obj):

self.obj = obj

self.c = 0

def __next__(self):

try:

r = self.obj[self.c]

self.c += 1

return r

except IndexError:

raise StopIteration

def __iter__(self):

return self

next()

return

__next__

__iter__

and

40 of 54

for 文の1行目で起こっていること

この1行で色々起きてます

40

任意の

object

__iter__

__getitem__

or

next()

return

__next__

__iter__

iter()

and

iterator

object

return

for o in obj:

iterator protocol

Protocol?

特に名称はなさそう(´・ω・`)

41 of 54

独自のデータ型に iter(), next() Adapter

  1. __iter__() メソッドを実装した独自クラスを定義します

  • このコンテナをforに与えると、辞書のキーのアルファベット順に、そのキーの値が繰り返されます

41

class MyContainer:

def __init__(self, mapping):

self.keys = sorted(mapping) # ソートして保持

self.mapping = mapping # 値返し用

def __iter__(self): # for文で呼ばれる

return MyIterator(self)

def __getitem__(self, idx): # MyIteratorから呼ばれる

return self.mapping[self.keys[idx]]

>>> list(MyContainer({'foo': 1, 'bar': 2, 'poke': 3, 'ah': 4}))

[4, 2, 1, 3]

42 of 54

Iterator Protoclの抽象基底クラス

  1. http://docs.python.jp/3/library/collections.abc.html 

  • 「抽象基底クラス」を継承して、Protocol実装を強制できます
  • abc はそのためのモジュール

42

43 of 54

継承によるProtocolの強制

  1. 継承によるInterfaceの強制

  • collection.abc を継承すれば実装忘れは防げる
  • 対応Protocolを明示したい場合にも良い

43

from collections.abc import Iterator

class MyIterator(Iterator):

pass

>>> MyIterator()

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

TypeError: Can't instantiate abstract class MyIterator with abstract methods __next__

Explicit is better than implicit.

44 of 54

(´・ω・)おつかれさま(・ω・`)

44

45 of 54

まとめ

46 of 54

まとめ

  1. len() は Adapter Pattern
    1. って言われると納得感あるよね(あるよね?)

  • len() ひとつ見ても経緯と議論の歴史がある
    • len() vs .length の議論を頭良い人達がしてないわけない
    • 歴史に学ぼう

  • 初級から中級へ進むには
    • ひたすら情報を読む、歴史を追う
    • 自分なりに解釈する
    • Pythonで色々実装してみる

46

47 of 54

公式リファレンスに�多くの情報が載っている

原典を調べよう

PEPを読んでみよう

47

48 of 54

References: Python公式リファレンス

  1. デザインと歴史 FAQ - Python 3 ドキュメント - Python にメソッドを使う機能 (list.index() 等) と関数を使う機能 (len(list) 等) があるのはなぜですか?�https://docs.python.jp/3/faq/design.html#why-does-python-use-methods-for-some-functionality-e-g-list-index-but-functions-for-other-e-g-len-list

48

49 of 54

References: Python公式リファレンス

  1. Python-2.4 ライブラリリファレンス イテレータ型�http://docs.python.jp/2.4/lib/typeiter.html
    1. バージョン 2.2 で追加
  2. 8.3. collections — 高性能なコンテナ・データ型 - Python 2.6ja2 documentation�http://docs.python.jp/2.6/library/collections.html#abcs-abstract-base-classes
    • バージョン 2.6 で変更: 抽象基底クラス (abstract base class) の追加
  3. 2. 組み込み関数 - Python 3 ドキュメント�http://docs.python.jp/3/library/functions.html#iter
  4. 4. 組み込み型 - Python 3 ドキュメント�http://docs.python.jp/3/library/stdtypes.html#iterator-types
  5. 8.4. collections.abc — コレクションの抽象基底クラス - Python 3 ドキュメント�http://docs.python.jp/3/library/collections.abc.html

49

50 of 54

References: PEP

  1. PEP 1 -- PEP Purpose and Guidelines | Python.org�http://sphinx-users.jp/articles/pep1.html (和訳)�https://www.python.org/dev/peps/pep-0001

50

51 of 54

References: blog等

  1. len が関数になっている理由 - methaneのブログ�http://methane.hatenablog.jp/entry/20090702/1246556675
  2. len が py3k でも 関数のままである理由 - methaneのブログ�http://methane.hatenablog.jp/entry/20090721/1248195293
  3. Solid Snakes or: How to Take 5 Weeks of Vacation - Hynek Schlawack�https://hynek.me/talks/reliability/
  4. オブジェクト指向と20年戦ってわかったこと – Qiita�http://qiita.com/shibukawa/items/2698b980933367ad93b4
  5. 新人プログラマに知っておいてもらいたい人類がオブジェクト指向を手に入れるまでの軌跡 – Qiita�http://qiita.com/hirokidaichi/items/591ad96ab12938878fe1
  6. Python を支える技術 ディスクリプタ編 #pyconjp – Qiita�http://qiita.com/knzm/items/a8a0fead6e1706663c22
  7. The History of Python.jp: ユーザ定義クラスのサポートの追加�http://python-history-jp.blogspot.jp/2009/04/blog-post_30.html
  8. 仮想継承とsingledispatch – atsuoishimoto’s diary�http://atsuoishimoto.hatenablog.com/entry/2016/08/04/095641
  9. The Zen of Python 解題 - 前編 – atsuoishimoto’s diary�http://atsuoishimoto.hatenablog.com/entry/20100920/1284986066

51

52 of 54

References: CPython code

  1. bool()実装 https://github.com/python/cpython/blob/1f06a680d/Objects/typeobject.c#L6081-L6127
  2. 真偽判定実装 https://github.com/python/cpython/blob/1f06a680d/Objects/object.c#L1314-L1336
  3. len()実装 https://github.com/python/cpython/blob/1f06a680d/Objects/typeobject.c#L5920-L5944
  4. len()のint値判定で呼ばれる実装 https://github.com/python/cpython/blob/1f06a680d/Objects/abstract.c#L1238-L1275

52

53 of 54

References: その他

  1. 実践 Python3�http://amzn.to/2vK2uHl
    1. Adapter Pattern, Iterator Pattern, Protocol, …

53

54 of 54

ありがとうございました

Thank you po!�

Questions?

@shimizukawa

54