ftnext / PyConTalkSummary

PyConで聞いたトークのサマリーをIssueに蓄積(arXivTimesリスペクト)
0 stars 0 forks source link

[PyConJP2017] len()関数がオブジェクトの長さを手にいれる仕組み #39

Open ftnext opened 3 years ago

ftnext commented 3 years ago

偶然スライドを見つけ、興味を持ったのでYouTubeのアーカイブを視聴

一言でいうと

len()関数、if文、for文の動きを掘り下げ、Pythonが使っているProtocolを紹介。 合わせてlenboolといった組み込み関数がAdapterパターンであることも示した

発表資料リンク

発表者/所属

shimizukawaさん(BeProud)

発表日付

2017/09/08

概要

対象者は「なぜlen"関数"?」「len関数ということは、Pythonはオブジェクト指向ではない?」という疑問を持っている人。

len関数はオブジェクトの__len__メソッドを呼び出すが、返り値がintかチェックしている。 intになることを保証している(TypeError) →これがAdapterパターン __len__メソッドはプロトコル。オブジェクトに実装されていて、len関数でも使う(オブジェクトとAdapterが通信する規約) プロトコルはオブジェクトの振る舞いを決めているもの

if文はbool関数を呼び出す。 bool関数もAdapter。オブジェクトの__bool__を呼び出す オブジェクトに__bool__がない場合、__len__を呼び出す。 __len__の返り値(intと期待)が持つと期待される__bool__を呼び出してTrue/Falseを得る

for文はiter関数を呼び出す(※) iter関数もAdapter。オブジェクトの__iter__または__getitem__を実行してIteratorを返す(※Iteratorパターンでもある) iteratorは対象のオブジェクトと現在何番目かを持っている

抽象基底クラス(例:Iterator)を継承して、Protocol実装を強制できる。 これにより対応Protocolを明示できる

(※)

>>> a = [1,2,3]
>>> type(iter(a))
<class 'list_iterator'>
>>> d = {"apple": 100}
>>> type(iter(d))
<class 'dict_keyiterator'>

[以下はオプション]

新規性・差分

len関数があることで

https://docs.python.org/ja/3/faq/design.html#why-does-python-use-methods-for-some-functionality-e-g-list-index-but-functions-for-other-e-g-len-list

対して、x.len()を目にした場合、私はその時点でxが何らかのコンテナであり、それが標準のlen()を持っているクラスを継承しているか、インターフェースを実装していることを知っている必要があります。

トークで知って試したいこと

感想

ftnext commented 3 years ago

Adapterパターンについて

『Java言語で学ぶデザインパターン入門』

Adapter=Wrapper

len関数を例にすると

メリット

Adapterパターンは既存のクラスには全く手を加えずに、目的のAPIに合わせようとする 例

len関数を変えることなく、Adapteeの__len__の中身を変えられる

len関数(Adapter)を使う側は中の処理を知らなくていいので、使う側のコードを変えずにlen関数(Adapter)のコードを変えられる

ftnext commented 3 years ago

__getitem__ を実装して作る、ソートしたキーの順に辞書のバリューを返すコンテナ

※スライドではIteratorの理解のために独自実装している認識(コンテナの__iter__で呼び出している) 参考:スライドの写経 + 抽象基底クラスの継承を試したコード

class MyContainer:
    def __init__(self, mapping):
        self.keys = sorted(mapping)
        self.mapping = mapping

    def __getitem__(self, idx):
        return self.mapping[self.keys[idx]]

Python 3.9で動作確認

>>> mc = MyContainer({"foo": 1, "bar": 2, "poke": 3, "ah": 4})
>>> for i in mc:
...   print(i)
...
4
2
1
3
>>> list(mc)
[4, 2, 1, 3]