はじめに
PyCon JP 2017に素晴らしい発表があります。
プレゼンテーション:len()関数がオブジェクトの長さを手にいれる仕組み | PyCon JP 2017 in TOKYO
#pyconjp 2017のスライド。タイトルに興味惹かれ、知的興奮のパレードでした。lenやboolはAdapterパターンで実装されている!オブジェクトが__len__を持ってさえいればlenで長さが返せる!(プロトコル)積読 https://t.co/l0F3yTWk4o / “Pythonはどうやってlen関数で長さ…” https://t.co/KTVmAJBXVe
— nikkie / にっきー (@ftnext) 2021年2月12日
この発表をアーカイブで見て感銘を受けましたし、Pythonの特殊メソッドに開眼するきっかけになりました。
さて、私は変更しやすいソフトウェアを書けるようになりたくて設計のインプット・アウトプットもしています。
最近だと「ちょうぜつ本 読書ログ」シリーズですね。
ちょうぜつ本でAdapterパターンを学んだことで、上の発表に出てきた「len()関数はAdapter Pattern」という点だけどうにも飲み込めなくなってしまいました。
3連休のあたりはずっと考えていて結構苦しかったのですが、現時点の理解をアウトプットして自分の中で区切りをつけます
目次
- はじめに
- 目次
- PyCon JP 2017 「len()関数がオブジェクトの長さを手にいれる仕組み」
- 現時点の考え:2つのインターフェースが合わないときのアダプタではないと思う
- Adapterパターン:2つのインターフェースが合わないときに変換して解決
- len()関数は特殊メソッド__len__()を呼び出す
- 飲み込めていないところ:「2つ」のインターフェースの変換
- 終わりに
PyCon JP 2017 「len()関数がオブジェクトの長さを手にいれる仕組み」
素晴らしいのでみんな見て!
(SlideShareは下スクロールで全スライドが見える(広告も挟まってる)ように変わりましたね)
slide=12が「len()関数はAdapter Pattern」のスライドです。
https://www.slideshare.net/shimizukawa/how-does-python-get-the-length-with-the-len-function/12
アーカイブは5:00過ぎです。
最初に聞いたときは「そうか、len()関数はAdapterなのかー」と疑問に思うことなく飲み込みました。
むしろ、「こんなところでデザインパターン使われているんだ。Pythonすごい!」と鮮明に記憶に焼き付きました。
ところが、Adapterについて知ってしまったばかりに、「len()関数がAdapter」というのが納得できなくなってしまったのです。
現時点の考え:2つのインターフェースが合わないときのアダプタではないと思う
- デザインパターンのAdapterパターンは、2つのインターフェースが合わないときに間に入って変換するもの
- 2つのインターフェースはどちらも変えなくてよい
- Adapterが、ACアダプタのように間に挟まるため
- len()関数は引数のオブジェクトの
__len__()
を呼び出す- len()関数が関わるインターフェースは、特殊メソッド
__len__()
1つだけと私は捉えている
- len()関数が関わるインターフェースは、特殊メソッド
- インターフェースは1つだけなので、2つのインターフェースの間を埋めるAdapterパターンは持ち出せないように思われる
- なので、いまの私は「len()関数はAdapter Pattern」という主張には同意しません
- この点だけ解釈不一致ですが、PyCon JP 2017のこの発表は素晴らしいものです(私が重箱の隅をつついています)
Adapterパターン:2つのインターフェースが合わないときに変換して解決
結城先生のインタビューより
インタフェース(API)があわないとき、それをうまく変換してやって適合するようにしてあげる存在がいればいいんです。 それを一般に「アダプター」と呼びます。ACアダプターと同じですね。
ちょうぜつ本の説明と合わせて、私の中でのAdapterパターンはACアダプタです1。
直流と交流で2つのインターフェースが合わないので、アダプタを間に入れて変換します。
ACアダプタは交流を直流に変換するものです2。
『実践 Python 3』という本でも、デザインパターンは扱われています。
あるクラスが、互換性のないインタフェースを持つ別のクラスを利用したい場合、そのどちらのクラスも変更することなしに利用する。(2.1より)
まさにACアダプタですね。
2つのインターフェースを変えるのではなく、ACアダプタが間に入ります。
len()関数は特殊メソッド__len__()
を呼び出す
(今回言及しているPyCon JP 2017の発表の説明がおすすめなのですが、)Pythonの組み込み関数len()は、引数で渡されたオブジェクトの__len__()
を呼び出します。
https://docs.python.org/ja/3/reference/datamodel.html#object.__len__
Called to implement the built-in function len().
これは特殊メソッドという仕組みです。
先日の記事でも取り上げました。
__len__ に対するインタフェースは len() という組み込み関数になっている。(先日の記事中で参照した稲田さんのブログより)
飲み込めていないところ:「2つ」のインターフェースの変換
「len()関数はAdapter Pattern」という説明で飲み込めずにいるのは、Adapterが間に入る2つのインターフェースがあるのだろうかという点です。
- len()関数は引数のオブジェクトの
__len__()
を呼び出す- 呼び出した後、負値であれば例外送出などもするが、長さを尋ねる処理は引数の
__len__()
に委譲
- 呼び出した後、負値であれば例外送出などもするが、長さを尋ねる処理は引数の
- インターフェースは特殊メソッド
__len__()
1つだけではないか
直流と交流のようにインターフェースが2つあるのではなく、インターフェースは1つだけのように思うのです。
なので、2つのインターフェースを変えずに変換のために挟まるAdapterの出番がないように思われます。
Adapterパターンをlen()関数と一緒に使う例
こういうケースだったらAdapterパターンだよねと言えそうな例です。
ただしlen()関数がAdapterパターンというわけではありません。
まず、サードパーティの素晴らしいPythonライブラリawesome
があるとします。
非常に革新的だったので、使っていきたいです。
awesome
の中心となるAwesomeCoreContainer
クラスには、長さを返すメソッドlength()
が実装されていました。
awesome
の開発者の方はPythonを深くまでは知らなかったようで(その分他の言語の造詣が深かったのでしょう)、特殊メソッド__len__()
は実装されていません。
このAwesomeCoreContainer
を手元のコードでlen()関数と一緒に使いたいです。
len()関数は__len__()
を要求しますが、AwesomeCoreContainer
にはlength()
しかありません。
交流と直流のように、2つのインターフェースが合わない状況ですね。
ここにAdapterパターンを適用したクラスを導入します。
class SizedAwesomeCoreContainerAdapter: def __init__(self, target: AwesomeCoreContainer) -> None: self.target = target def __len__(self) -> int: return self.target.length()
このAdapterを使えば、len()関数もAwesomeCoreContainer
クラスも変えずに、インターフェースを一致させてlen()関数で長さを返せます。
len(SizedAwesomeCoreContainerAdapter(AwesomeCoreContainer(...)))
繰り返しますが、これは2つのインターフェースが合わない状態をAdapterのクラスを作って解決したもので、len()関数がAdapterパターンと示したものではありません。
終わりに
かつて聞いた「len()関数はAdapter Pattern」、デザインパターンを学んだことで飲み込めなくなってしまい、いったんアウトプットして自分の中では決着させました。
私はまだ見つけられていないのですが、Guido氏や多数のPython core開発者が「len()関数をはじめとする特殊メソッドの機構はAdapterパターン」という見解を表明していたら、この記事は反証されます。
- AC=alternating current(交流)、DC=direct current(直流)↩
- ref: ACアダプターとは? | 【ユニファイブ】ACアダプター&スイッチング電源メーカー↩