##
みなさんんこんにちは。
最所に自己紹介します。
私はTakayuki Shimizukawaです。
I have been living in BGC, Manila for 7 months now.
I love spicy food and my favorite Filipino dish is sisig.
I also love craft beer.
I have been using Python since 2004
My main activities are as follows:
* I am the accounting director of "PyCon JP Association"
* I work fully remotely for a Japanese company BeProud.
* I have translated and written 13 Python-related books in 13 years.
So, let's get started!
##
Pythonはどうやって`len()`関数で長さを手に入れているの。
ターゲット参加者は以下のように思っている方です。
* Pythonは入門したけど、なんかしっくりこないなあ、とか。
* Python、なんでlenはメソッドじゃなく関数なんだろう、とか。
* Pythonはオブジェクト指向じゃないの?
こういったところについて疑問をお持ちの方。
資料はPython初級から中級にあがっていこう、という方向けに構成しています。
##
トークのアジェンダです。
3つのパートがって、1つめの "How does len() get the length of an object?" がこのスライドで一番重要なところです。
2つめの "How if determinesh blah blah.." は、1つめを下敷きにして少し違う例を紹介します。
3つめはもう少し複雑な例ですが、今日は時間が限られているため割愛します。あとでスライドを参照してもらえればと思います。
最後にサマリーと参考文献があります。
それでは、パート1に進みましょう。
##
`len()`関数がオブジェクトになると手に入れる方法初級。唐辛子レベル1です。
唐辛子は難易度だと思ってください。
##
`len()`関数に渡した時に起こること、ということで、例として文字列を渡してみます。
4が返ってきました。
次に、文字列のunderscore underscore len underscore underscore、カッコ、を呼びます。
長いですね、これは dunder len メソッドと呼びます。
dunder lenを呼ぶと、4が返ってくるわけですね。これは1つめの結果と同じです。
実際のところ、 `len(obj)` が内部では `obj.__len__()` メソッドを呼んでます。
##
obj. `.__len__()` メソッドがあるんだったらそれを使えばいいじゃない?
JavaやJavaScriptでは`.len`や`.length`属性で長さを取れるじゃん。
Pythonは `.__len__()` メソッドなの何で?という感じがあります。
実は、 `len()` はもうちょっと仕事してます。
このグリーンの部分で、文字列オブジェクトが返してきた値が`int`なのかをチェックしています。
チェックして `int` じゃなかったら `TypeError` 例外を投げます。
こういうことをして `len()` 関数が `int` を保証しています。
ところで、スライド右上見ました?
唐辛子、実はすこしずつのびていってます。
##
`.__len__()` が `int` 以外の値を返すとどうなるのか・・・
確認するために、こういうコードを書いてみました。
例えば、`1.2` `float` 型を返すと、`float object can not be interpreted as an integer`、と言うエラーが起きます。
他にも、`sys.maxsize`を超えると、さらに別の例外が起きたりもします。
##
`len()`関数の役割は、渡されたオブジェクトの `.__len__()` メソッドを呼んで、取得した値をチェックして、呼び出し元に適切な値を返すとです。
これはAdapterパターンっていうやつで、インターフェース変換をやってるわけですね。
オブジェクトに対して`len()`関数をアダプトすることで、オブジェクトから値を取り出して適切な処理をして結果を返します。
これは、絵的に`.__len__()`が二つに分かれてますけど、左側は文字列オブジェクトがこのメソッドを持っているっていう実装です。右側のグリーンの四角に対して `.__len__()` が生えてるのは、`len()`関数はこのインターフェースを使いますよ、っていう風に読んでください。
##
ということでAdapterパターン、どっかで聞いたことがあると思うんですけれども、詳しくはWikipediaに任せるとして、絵で見るとなんとなくわかるかなと思います。
2口ソケットの100ボルトから240ボルトの交流を、直流の20ボルト3.25アンペアに変換する電源Adapterです。
Adapter をかますと、インターフェースを変換して適切な値が取れてきます。
入力が異常な場合は出力しない機能を持っている電源アダプターもありますね。
##
len関数は、特定のオブジェクトに対して Adapter として作用します。
特定の、というのは、`.__len__()` インターフェースで数値を返してね、というプロトコルが決まっていて、このプロトコルに適合するオブジェクトが対象となります。
もし、数値型以外を `len()`関数が受け取ったら、 `TypeError` を出します。
##
具体的には、`list`は`len()`アダプターに適合します。
`list.__len__()` メソッドは要素数を返ます。
要素数はlistオブジェクト自身が知っていて、要素数は3なので、 `.__len__()` は3を返します。
`len()` Adapter がこの値をチェックして最終的に3を返します。
##
`dict` にも `.__len__()` があって、キーの数を返します。
`dict`の例として、age: 999 ,name:Kuya であれば、キーの数は2つなので2を返します。
じゃあ、カスタムデータ型にlen Adapter を使ってみましょう。
ここまで言ってきたように、 `.__len__()` メソッドがあれば、len Adapter に対応できるので、 `.__len__()` を実装すれば動作します。
この Random class は、 `.__len__()` メソッドが `random.randint(0,10)` の結果を返します。
長さが呼び出し毎に変わる、風変わりなクラスですね。
このように、len Adapterに適合するクラスを簡単に実装できます。
##
プロトコルというのはオブジェクトの振る舞いを決めているものです。
`len()` 関数は `obj.__len__()` メソッドがあれば動作します。
言い換えると、長さの概念を持つオブジェクトは `.__len__()` メソッドを実装する必要があります。
つまり、ユーザーが "len関数に渡したら長さ返してくれるんでしょ" と動作を期待するようなオブジェクトには、 `.__len__()` メソッドを実装しておきましょう。
この `.__len__()` メソッドを実装する事を、「長さのプロトコルを実装する」と言います。
##
プロトコルって言ってきましたけど、Pythonドキュメントのどこにプロトコルの説明が書いてあるのか、と聞かれた事があります。
それで探してみたところ、Python公式ドキュメントの中で4,5ページ分くらい出てきています。特にシーケンスとかイテレーターとか、そういう用語の周りにイテレータープロトコルだとかが登場します。
##
プロトコルがドキュメントに登場したのはPython 2.2でした。Pythonにclassが導入された頃です。
Pythonには、Python Enhancement Proposal: PEPというものがあって、そこにプロトコルの定義があるか、というのも調べてみました。
PEPでのプロトコルへの言及は、2017年5月のPEP-544で初登場しています。
PEP-544はタイプヒント導入に向けてプロトコルという用語を定義するために作られたPEPのようです。
##
`collections.abc` のドキュメントは、プロトコル一覧の代わりに使えそうです。
プロトコル一覧を探していて、これを見つけました。
ここに、コレクションの種類によってサポートするべきメソッドが書かれています。
Sizedコレクションは `.__len__()` メソッドを持つ、と書かれています。
##
ここまでのまとめ。
`len()` は、objectに対するAdapter です。
プロトコルは、オブジェクトとAdapterが通信する規約です。
##
ここまで「lenは Adapter」と説明してきました。
しかし、人によっては「adapterって言っても `int` かどうかチェックしてるだけの関数でしょう?`.length` 属性にしても良かったのでは?」と思うかもしれません。
以下はPython公式ドキュメントの「デザインと歴史FAQ」からの引用です。
As Guido said: (b) 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.
##
さて、これでパート1はおわりです。
ここまで唐辛子1個でしたけれども、次は、ここまでの内容をベースにif文での例を紹介します。
##
if文がオブジェクトのtrue falseを判断する方法。
唐辛子レベルは2個です。
##
if文のルール。
if文の例ですが、何かのオブジェクトが真だったら"trueだ"と表示されて、真じゃなかったら"Falseだ"と表示されるプログラムです。
内部では自動的にブール関数に渡されていて、実行されています。
ということで、bool()関数を関数では無くAdapterと呼んでいきたいと思います。
##
bool Adapterはオブジェクトの真偽を判別します。
例えば123という数字を渡せばTrueが返ってきます。
中で `obj.__bool__()` を呼んでるんでしょ?と予想できますが、まあ、そのとおりですね。
さっきまでのlenと同じですね。
ということで、次は文字列でやってみます。
##
あれっ、文字列オブジェクトの `.__bool__()` メソッドを直接呼んだら
`Attribute Error 'str' object has no attribute '__bool__'` というエラーになってしまいました。
では、`bool()` の仕様をドキュメントで確認してみましょう。
##
以下は公式リファレンスからの引用です。
* オブジェクトが偽とみなされる条件。クラスが `.__bool__` または `.__len__()` メソッドを定義していれば、それらのメソッドが整数ゼロまたはbooleanのfalseを返す時は偽です。
* 真とみなされる条件。偽じゃないやつ。
このように書かれています。
さっきのlen adapterよりもちょっとチェックする事が増えていて、lenよりは仕事してそうですね。
##
bool Adapterの仕組みを絵で紹介します。
`.__bool__` メソッドが無い場合は `bool(len(obj))` bool、カッコ、len、カッコ、オブジェクト、相当の処理を行います。
まずは `.__bool__` メソッドからみていきます。
最初に、 `.__bool__` メソッドがあるかチェックします。
あれば、それを使ってブール値取り出します。ブール型が返ってくればその値を使い、そうでなければ `TypeError` 例外を投げます。
`.__bool__` メソッドが無かった場合、内部でlen adapter相当の処理を行って、その結果の値をブール相当の処理に渡しています。そうすると、lenが必ず数字を返すことは分かっているので、 `int.__bool__()` メソッドが使えるんですね。
これがbool Adapter の仕事です。CPythonでのbool Adapterの実装コードを裏取りのために調べたので、興味のある方は読んでみて下さい。唐辛子レベルは6です。
##
次は、カスタムデータ型にbool Adapterを使ってみましょう。
PositiveIntクラスを実装します。
これは正の整数の場合に真になります。
やってることは、 `int` を継承してselfがゼロより大きかったらTrueを返す `.__bool__` メソッドを実装しています。
実際やってみると、10はTrueになり、マイナス3はゼロ以下なのでFalseになります。
ここまでで、if文での真偽判定についての話が終わりになります。
##
パート3は、for文がオブジェクトの繰り返しを取得する方法についてですが、時間が足りないため割愛します。
##
最後ちょっとまとめたいと思います。
今日のトークではlenとboolの例を通して、Adapterとプロトコルについて紹介してきました。
lenはAdapterパターンと言われるとなんか納得感あるよね?
len関数だなんてPythonはオブジェクト指向じゃない!という感じが無くなってくれたら嬉しいです。
デザインパターンが好きな人はadapterパターンとして見れば納得感があるんじゃないかと思います。
##
あと、`len`関数 対 `.length`属性について。
自分の好みはいったん置いておきましょう。
`len()`関数一つ見ても、言語仕様が今の形になっていることについて多くの議論が行われています。
私も資料を作りながら歴史を追ってたら、膨大な情報が見つかりました。
そういう議論を追って、実装コードを読んでみるのも、プログラミング言語の勉強に良いと思います。
また、公式ドキュメントには多くの情報が載っています。 今回の引用元ほとんど公式リファレンスでした。 PEPを読んでみることもチャレンジの1つになると思います。
初級から中級に進むには?
このスライドで参照している多数の資料をひたすら読んでみましょう。
そして、自分なりに解釈して、Pythonでいろいろ実装して、「何か動きが予想と違うぞ」とか「一貫性がないぞ」とか「ここの部分はプロトコルの名前が付いてないぞ」などの発見をして行くのも面白いと思います。
そうすることで、知識が自然に身に付くと思います。
##
ここから先は参考文献です。
##
参加されたみなさん、イベントスタッフのみなさん、ありがとうございました。
これで私の発表は終わりになります。
何かご質問とかある方はいらっしゃいますか?