ゴミ箱の中のメモ帳

まだ見ぬ息子たちへ綴る手記

久しぶりにしびれたコード attrdict

前の記事に書いた通りPythonのPeeweeをいくらか読んだ。その中で久々にしびれるコードを見たので是非とも紹介したい。

久々と言いつつ人のコードを読むことはあまりないので経験が少ない。やはり人のコードを読むのはいいものだ。なのでこのコードは一般的なものかも知れない。だがこの素晴らしさを是非とも知っていただきたい。

 

みんなのRaspberry Pi入門 第2版 [対応言語:Python] (リックテレコムの電子工作シリーズ)

みんなのRaspberry Pi入門 第2版 [対応言語:Python] (リックテレコムの電子工作シリーズ)

 
Pythonスタートブック

Pythonスタートブック

 

====
Pythonのディクショナリを使っていると、どうしてもキーの指定が面倒くさくはないだろうか。

a = {'a': 1, 'b': 2, 'c': 3}
a['a']
a.d = 4

 ディクショナリのキーを文字列とする場合はクォートで囲む必要があるし、アクセスするときはブラケットで囲む必要が有る。これはPythonのディクショナリでは文字列だろうと数値だろうとタプルだろうと集合だろうとキーとしてイミュータブルな値ならなんでも指定出来るようになっている都合になる。

だがしかし、ここまで汎用性が高いことを望まず、単に文字列をキーとして指定するくらいが主な使い方ではないだろうか。そうなると少々面倒臭い。

 

そしてそんな時は空のクラスを使って辞書を再現することがある(私は)。

class classdict:
    pass

a = classdict()
a.a = 1
a.b = 2
a.a

 これはちょっとしたオプションの保持には便利なのであるが、一度インスタンスを作った後に代入していく必要があるので数が多くなるとこれはこれでまた面倒臭い。だがまぁディクショナリを使うよりは多少手軽であるために私はよく使っていた。

これはもちろん先に書いている通り「文字列をキーと指定することに限定した」使い方なので、ディクショナリでキーとして指定できる数値やタプルなどのイミュータブルな値は使えない。プロパティとして指定可能な名前のみだ。

 

そしてそんなちょっと手間のかかる方法を使っていたのだがPeeweeのコードを読んでいると素晴らしいクラスを見つけた。

まさに私が望んでいたディクショナリライクなクラスだ。

class attrdict(dict):
    def __getattr__(self, attr):
        return self[attr]

 この力強さがわかるだろうか。コレほどシンプルで力強いコードは中々見たことがない。

最初は何をしているのか理解できなかった。それ程にシンプルで力強い。

私のような低級プログラマにはコレを理解できないかも知れないので簡単に解説しておく。

もちろん単純にはディクショナリ(dict)だ。dictを継承したクラスで変更しているのはもちろん「__getattr__」メソッドだけ。これはクラスの属性として指定された値が見つからない場合に呼び出されるメソッドになる。

なので、存在しないプロパティを指定された際に呼び出される。通常は「AttributeError」例外を発生させる箇所だ。だがこれをAttributeErrorではなくディクショナリのキーに変換してアクセスさせる。少々強引な感じもするが、dictを継承しているのでこの問題も解決される。これは後述する。

 

使い方としてはこうだ。

a = attrdict(a=1, b=2, c=3)
a.a
a.d = 4

 素晴らしい。先の私が使っていた空クラスの問題が解決されている。インスタンス作成時にキーワード引数として代入できる。ディクショナリと違いクォートが省略でき、アクセスもプロパティとして行える。キーワード指定なので空クラスと同じく属性値に使える名前しか命名できないがコレは問題はない。

 

だがこのクラスはコレだけの問題を解決するわけではない。空クラスを使う場合はプロパティの一覧が欲しい時や、ループで全てを処理する際にまた別の方法を使わないといけないが、このクラスはdictを継承しているのでデフォルトでその機能が備わっている。

a.keys()
a.items()

 全く問題ない。こんな素晴らしいクラスをなぜ私は知らなかったのか。

 

そして先に書いた問題、「__getattr__」で例外を出さないのであれば存在しないプロパティにアクセスした時はどうなるのか。

空クラスであればもちろん「AttributeError例外」が発生される。

a.e

Traceback (most recent call last):

File "", line 1, in

AttributeError: 'classattr' object has no attribute 'e'

 

だが素晴らしクラスならこうなる。

a.e

Traceback (most recent call last):

File "", line 1, in

File "/home/kmasaya/Devel/attrdict/attrdict.py", line 6, in __getattr__

return self[attr]

KeyError: 'e'

 

プロパティとしてのアクセスはディクショナリのキーに変換されるので、存在しないプロパティへのアクセスは存在しないキーへのアクセスと変換され「KeyError例外」となる。これもよりディクショナリっぽい動きだろう。

 

素晴らしい。この素晴らしいコードを皆さん是非使って欲しい。

Peeweeではこのクラスが使われている箇所と使われていない箇所が有るが、これはattrdictが導入されたのが割と最近であることと、定数を置き換えることを目的として導入されたために統一されていないのかも知れない。

attrdictが導入されたコミットはこちら。

 

https://github.com/coleifer/peewee/commit/3f86b9fa2f6acf5df4ad5befe77eae9578daf6d9