タイトル/名前 | 更新者 | 更新日 |
---|---|---|
Python/codepiece/特殊メソッド/2.ディスクリプタ用__set__,__get__ | msakamoto-sf | 2009-03-22 11:38:04 |
Python/codepiece/特殊メソッド/1.属性参照の__setattr__,__getattr__ | msakamoto-sf | 2009-03-21 23:21:52 |
Python/codepiece/特殊メソッド/0.基本の特殊メソッド | msakamoto-sf | 2009-03-21 23:21:17 |
Python/codepiece/関数(3) デコレータ("@") | msakamoto-sf | 2009-03-21 22:42:51 |
Python/codepiece/関数(2) 結合メソッド, 非結合メソッド | msakamoto-sf | 2009-03-21 21:45:44 |
日記/2009/03/21/Perlの「型グロブ」メモ | msakamoto-sf | 2009-03-21 20:39:30 |
Python/codepiece/継承(2) | msakamoto-sf | 2009-03-21 20:10:00 |
Python/codepiece/継承(1) | msakamoto-sf | 2009-03-21 19:49:22 |
Effective Python Programming | msakamoto-sf | 2009-03-21 19:30:07 |
日記/2009/03/21/サムネイル画像をクリックすると大きい画像がポップアップするJavaScriptライブラリ | msakamoto-sf | 2009-03-21 11:57:44 |
__{get|set}attr__ と似ていて非常に紛らわしい特殊メソッドに "__get__", "__set__", "__del__" がある。
これは「ディスクリプタ(Descriptor)」と呼ばれる特殊なオブジェクトを作る時に実装するメソッドである。
分かりづらい概念で、Python Language Reference (2.5) にも "3.4.2.2 Implementing Descriptors" としてさらっと説明されているだけ。しかも、標準ライブラリでgrepしてもtestコードやinspectモジュールのドキュメント部分くらいしかヒットしないという、「これ本当に使おうと思って実装した機能か?」と疑いたくなるような仕様である。
見た方が早いので、先に __get__() だけ実装したDescriptorを動かしてみる。
>>> class D1(object): # これがディスクリプタになる。 ... def __get__(self, obj, type): ... print repr(self) ... print repr(obj) ... print repr(type) ... print "in __get__()" ... return 'abc' ... >>> class C(object): ... x = D1() # ディスクリプタは、親のオブジェクトがあって初めて意味を持つ。 ... y = 'ABC' ... >>> c = C()
ここでインスタンスcの属性x, yにアクセスしてみる。
>>> c.x <__main__.D1 object at 0x00AF7C70> <__main__.C object at 0x00AF7CD0> <class '__main__.C'> in __get__() 'abc' >>> c.y 'ABC'
属性yの場合はstringオブジェクトがそのまま評価されるが、属性xの場合は "__get__"特殊メソッドが実装されたディスクリプタであるため、"__get__"が実行されている。
ここで、クラス属性としてxを参照してみる。
>>> C.x <__main__.D1 object at 0x00AF7C70> None <class '__main__.C'> in __get__() 'abc'
興味深いことにクラス属性からだと__get__の二番目の引数, obj にNoneが渡ってくる。
これがディスクリプタの面白い点で、 Effective Python Programming 95Pに "descriptors are how 'self' gets inserted
as the first argument of a method" と書かれている機能である。
Python Language Reference(2.5), "3.4.2.3 Invoking Descriptors"にもあるように、クラス属性としてアクセスされるか、インスタンス属性としてアクセスされるかで obj が変わる。
インスタンス属性経由 : type(a).__dict__['x'].__get__(a, type(a)) クラス属性経由 : A.__dict__['x'].__get__(None, A)
ちなみに __get__() だけ実装したのを「Non-data descriptor」、__get__(), __set__()の両方を実装したのを「data descriptor」と呼ぶらしい。
ところで冒頭で「標準ライブラリでgrepしても~」と書いたが、実はC言語で同じ動作原理で実装されているのが staticmethod(), classmethod() らしい。"self" の受け渡しをハイジャックできるディスクリプタの機能を活用している。
Effective Python にclassmethod()をPythonレベルで実装した例が載っている。
こちらでは、staticmethod()をPythonレベルで実装するサンプルを作ってみたので解説する。
>>> class StaticMethod(object): ... def __init__(self, f): ... self.f = f ... def __get__(self, obj, type): ... def newfunc(*args, **kw): ... return self.f(*args, **kw) ... return newfunc ... >>> class D: ... @StaticMethod ... def f(x): ... print x ... >>> D.f(10) 10
class Dから見ていくと、
>>> class D: ... @StaticMethod ... def f(x): ... print x
これは
class D: def f(x): printx f = StaticMethod(f)
となる。StaticMethodクラスのインスタンスに置換される。
次に
D.f(10)
を見ると、まず"D.f"の部分が評価される。これはクラス属性としてアクセスしているので、
D.__dict__['f'].__get__(None, D)
となる。StaticMethodの__get__()は、デコレートしたときの元関数をラップした関数オブジェクトを返している。
def __get__(self, obj, type): def newfunc(*args, **kw): return self.f(*args, **kw) return newfunc
よって、
D.f(10)
は
newfunc(10)
となり、元の関数fがそのまま動く。
・・・という風に、selfをハイジャックしてどうのこうのしたい場合には上手く使えるようだけど、他の使い方が今の時点ではイマイチ思い浮かばない。まぁ、そのうちどこかのライブラリを読んでいる内に出くわすかも知れない。
属性参照、およびメモリ節約の為の__slots__属性については例によりEffectivePythonが分かりやすくまとまっている。
Python Document (2.5) でも、Language Referenceに "3.4.2 Customizing attribute access" として説明がある。
t_specialattrs_attr.py :
class C(object): def __getattr__(self, name): print "__getattr__(self, " + name + ")" if name in self.__dict__: #return self.__dict__[name] # 新形式でない場合はこちら。 return object.__getattr__(self, name) # 新形式ならこっち。 else: print 'no key:', name return None def __setattr__(self, name, value): print "__setattr__(self, " + name + ", " + repr(value) + ")" #self.__dict__[name] = value # 新形式でない場合はこちら。 object.__setattr__(self, name, value) # 新形式ならこっち。 def __delattr__(self, name): print "__delattr__(self, " + name + ")" #del self.__dict__[name] # 新形式でない場合はこちら。 object.__delattr__(self, name) # 新形式ならこっち。 c = C() c.v1 = 'abc' print c.v1 print c.v2 print repr(c.__dict__) del c.v1 print repr(c.__dict__)
> python t_specialattrs_attr.py __setattr__(self, v1, 'abc') ... c.v1 = 'abc' abc ... print c.v1 __getattr__(self, v2) ... print c.v2 no key: v2 None {'v1': 'abc'} ... print repr(c.__dict__) __delattr__(self, v1) ... del c.v1 {} ... print repr(c.__dict__)
基本的には Python Language Reference の "3.4 Special method names" を参照。但し "3.4.2 Customizing attribute access" 以下と "3.4.3 Customizing class creation", "3.4.8 Coercion rules" は難しいので後回し。
とりあえずぱっと見で分かりやすい基本的な特殊メソッド(__repr__, __str__, __len__, __nonzero__, __call__)のオーバーライド例を試してみました。__init__ については省略。
なお、"__del__"については Effective Python Programming の63Pに "__del__ is not your friend" とあるので、避けておく。
__del__ often considered harmful
- C++/Java-ism
- __del__ breaks garbage collector
- non-deterministic
- doesn't always get called usefully when Python is exiting
- use a weakref in some cases
とのこと。
コードピース(t_basic_specialattrs.py) :
class C(object): def __repr__(self): return 'repr() is called.' def __str__(self): return 'str() is called.' def __len__(self): print 'len() is called.' return 100 def __nonzero__(self): print 'convert to boolean.' return False def mycall(self): print 'this is callable object.' return None c = C() print repr(c) print str(c) print len(c) print bool(c) C.__call__ = mycall print c()
結果:
> python t_basic_specialattrs.py repr() is called. ... repr(c) str() is called. ... str(c) len() is called. ... len(c) 100 convert to boolean. ... bool(c) False this is callable object. ... c() None
なお基底クラスの特殊メソッドも呼びたい場合には、superやobject経由になります。
class C(object): def __repr__(self): print 'repr() is called.' return object.__repr__(self) def __str__(self): print 'str() is called.' return object.__str__(self)
こんな感じにもできます。
# repr(c) : repr() is called. <__main__.C object at 0x00AF8830>
# str(c) : str() is called. repr() is called. <__main__.C object at 0x00AF8830>
デフォルトのstr()はrepr()にバイパスされるみたいです。
2009年3月の段階で、果たしてデコレータを実際に使うのかどうか疑問なので、参考ページを載せるに留めておく。
「関数として呼び出し可能なオブジェクトを返す関数」で次々と元の関数を包み込んでいく感じ。クロージャとの合わせ技でもあるので、個人的には黒魔術の類であんまり使いたくない技法。まぁ、今の時点では。そのうち「デコレータマンセー!」になる可能性も0じゃない。
とにかく「デコレータ」として使える関数 or クラスを作るのがまずしんどい。単純に置き換えるだけなら以下のように、普通の関数でもデコレータとして使える。
>>> def d1(func): ... print "Hello, Decorator!" ... >>> @d1 ... def f1(arg): ... print "this line will be never executed." ... Hello, Decorator!
デコレートした時点でデコレータd1関数が実行されている。
>>> f1 >>> f1() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'NoneType' object is not callable
そしてf1はd1()の戻り値、つまりNoneになってしまっている。これはつまり、
f1 = d1(f1)
と同じ事。
実用するならデコレータ関数は「呼び出し可能オブジェクト」相当を返す必要がある。クロージャでも良いし、__call__を実装したインスタンスでも良い。いずれにせよデコレータ関数が実行される場合は、第一引数にデコレート対象の呼び出し可能オブジェクトが渡される点に注意する。
多段デコレートも可能・・・なんだけど、面倒くさいなぁ。
t_decorator2.py :
class deco1(object): def __init__(self, func): self.__func = func def __call__(self, *args, **kw): print "entering to deco1" self.__func(*args, **kw) print "exit from deco1" def deco2(func): def wrapper(*args, **kw): print "entering to deco2" func(*args, **kw) print "exit from deco2" return wrapper @deco1 @deco2 def f1(*args, **kw): print repr(args) print repr(kw) f1("abc", "def", a=123, b=456)
>python t_decorator2.py entering to deco1 entering to deco2 ('abc', 'def') {'a': 123, 'b': 456} exit from deco2 exit from deco1
Pythonの場合はインスタンスの生成も関数呼び出しも同じ構文が使えてしまう為、上記のデコレートは下記のようになり、あっさり動いてしまう。
f1 = deco1(deco2(f1))
しかしこれ、デコレータに引数を渡すともっと相当ややこしいことになる。
@deco(args) def foo():pass
→
foo = deco(args)(foo)
→
_deco = deco(args) foo = _deco(foo)
呼び出し可能オブジェクトを2回も作らなくてはいけない!
t_decorator3.py :
class deco1(object): def __init__(self, *args): print "deco1 is initialized with args : ", repr(args) self.__args = args def __call__(self, func): print "deco1 wraps func : ", repr(func) def wrapper(*args, **kw): print "entering to deco1(", repr(self.__args), ")" func(*args, **kw) print "exit from deco1" return wrapper def deco2(*dargs): print "deco2 is initialized with args : ", repr(dargs) def decorate(func): print "deco2 wraps func : ", repr(func) def wrapper(*args, **kw): print "entering to deco2(", repr(dargs), ")" func(*args, **kw) print "exit from deco2" return wrapper return decorate @deco1(1, 2, 3) @deco2("ABC", "DEF") def f1(*args, **kw): print "in f1() : " print repr(args) print repr(kw) f1("abc", "def", a=123, b=456)
> python t_decorator3.py deco1 is initialized with args : (1, 2, 3) deco2 is initialized with args : ('ABC', 'DEF') deco2 wraps func : <function f1 at 0x00AF6EF0> deco1 wraps func : <function wrapper at 0x00AF6F30> entering to deco1( (1, 2, 3) ) entering to deco2( ('ABC', 'DEF') ) in f1() : ('abc', 'def') {'a': 123, 'b': 456} exit from deco2 exit from deco1
もう何がなにやら。しかもデコレータは引数の有無で、どのタイミングでデコレート対象のオブジェクトが渡るかが見事にずれて仕舞う為、兼ねさせる事ができない。一旦引数有りで作ったデコレータは、簡単には引数無しでデコレートさせることが出来なくなってしまう。
現実に使うにはこれだけじゃ機能が足りない。デコレート対象の関数の名前空間(__dic__)や__doc__, __name__属性の引き継ぎを行えないと困る。というわけで、functools.wraps()関数がEffectivePythonで紹介されている。
import functools def deco(func): print "decorating ..." @functools.wraps(func) def wrapper(*args, **kw): print "entering to deco" func(*args, **kw) print "exit from deco" print "decorated!" return wrapper @deco def f1(*args, **kw): """ f1 document """ print "in f1() : " print repr(args) print repr(kw) print "__doc__ = ", f1.__doc__ print "__name__ = ", f1.__name__ f1("abc", "def", a=123, b=456)
> python t_decorator4.py decorating ... decorated! __doc__ = f1 document __name__ = f1 entering to deco in f1() : ('abc', 'def') {'a': 123, 'b': 456} exit from deco
言語機能としては興味深いが、実際のアプリ開発のレベルで簡単に使えるか/使いたくなるかと言われれば・・・という感じ。
ライブラリを開発する場合は恐らく強力なツールになるだろうけど、テストをかなり注意深く行わないと、思いっきり嵌りそうな予感。
PythonにはJavaのstaticメソッドと同じような意味のメソッドは無いようです。
>>> class C(object): ... def m1(): ... print 'static class method' ... >>> C.m1() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unbound method m1() must be called with C instance as first argument (got nothing instead)
上記エラーメッセージに"unbound method"という単語が出てきます。「非結合メソッド」と訳すようです。
エラーメッセージの内容としては、「非結合メソッド m1() はクラスCのインスタンスを第一引数に取る必要があります」(実際は指定されてないのでTypeError)という意味です。
「非結合メソッド」があれば「結合メソッド」もあります。以下のページがわかりやすく解説してありますので、まずはこちらを参照。
クラスのメソッドには次の3つの属性が設定されます。
参考:
というわけで、軽く実験してみます。
>>> class C(object): pass ... >>> def m1(self): print 'aaa' ... >>> m1 <function m1 at 0x00AF5CB0>
空っぽのクラスと、適当な関数を用意しました。続いて、関数をクラスのメソッドに設定します。
>>> C.m = m1 >>> C.m <unbound method C.m1> >>> c = C() >>> c <__main__.C object at 0x00AF7CB0> >>> c.m <bound method C.m1 of <__main__.C object at 0x00AF7CB0>>
"C.m" だと非結合メソッド、"c.m" だと結合メソッドとしてアクセスされるのが分かります。
>>> c.m.im_class, c.m.im_func, c.m.im_self (<class '__main__.C'>, <function m1 at 0x00AF5CB0>, <__main__.C object at 0x00AF7CB0>) >>> C.m.im_class, C.m.im_func, C.m.im_self (<class '__main__.C'>, <function m1 at 0x00AF5CB0>, None)
非結合メソッドの方の "im_self" はドキュメントの通り、Noneになっています。
"3.11.4 Methods"にありますが、im_funcとim_selfを使えば
c.m(arg1, arg2, ...)
を
c.m.im_func(c.m.im_self, arg1, arg2, ...)
と書けるようです。
>>> c.m.im_func(c.m.im_self) aaa
確かに呼べています。
以上。
なおクラスメソッドやJavaのstaticメソッドに相当する機能は、classmethod(), staticmethod() をデコレータとして使うことで実現できるようですが、それについては後日調べてみます。
基底クラスのメソッドを実行する手法を示します。
新形式の場合はsuper(クラス, インスタンス)関数を使って以下のように実装できます。
class S(object): def saySpam(self): print 'Spam' def __init__(self, name): self.name = name class C(S): def __init__(self, name, age): super(self.__class__, self).__init__(name) self.age = age def saySpam(self): super(self.__class__, self).saySpam() print 'Bacon'
実行してみます。
c = C('Spam', 24) c.saySpam() print c.name, c.age
→
# c.saySpam() : Spam Bacon # print c.name, c.age : Spam 24
ちなみに、
super(self.__class__, self).saySpam() super(self.__class__, self).__init__(name)
の部分は
super(C, self).saySpam() super(C, self).__init__(name)
と書いてもOKですが、 Effective Python Programming 77Pの "Use self.__class__ instead of hardcoded
class names" の方が良いかなと思ってそちらにしてみました。
新形式でない場合はsuper()が使えない為、普通にインスタンスをself引数として渡します。
super(self.__class__, self).__init__(name) -> S3.__init__(self, name)
super(self.__class__, self).saySpam() -> S3.saySpam(self)
新形式(objectから派生)のみ扱います。
自分で作成するベースクラスは、objectから派生させます。
class S(object): def saySpam(self): print 'Spam'
クラスSからもう一段派生させたクラスCを作成します。
class C(S): """ inheritance example. """ def sayHello(self, name): print 'Hello, %s.' % (name)
S, Cのインスタンスを作り、軽く動かしてみます。
c = C() s = S() c.saySpam() # >>> "Spam" c.sayHello('FooBar') # >>> "Hello, FooBar." print C.__doc__ # >>> " inheritance example."
c や s がクラスC, Sのインスタンスなのか真偽値で判定するコードは次の二種類があります。
パターン1: print s.__class__ is S # >>> True print s.__class__ is C # >>> Flase print c.__class__ is S # >>> False print c.__class__ is C # >>> True
オブジェクトの特殊属性"__class__"には、そのクラスオブジェクトが入りますので、それで判定する手法です。
一方、isinstance(インスタンス, クラス)関数を使う方法もあります。こちらですとクラスの継承元も見に行ってくれます。
パターン2: print isinstance(s, S) # >>> True print isinstance(s, C) # >>> False print isinstance(c, S) # >>> True : クラスSはクラスCの基底クラスです。 print isinstance(c, C) # >>> True
"__class__"特殊属性による判定は、 Effective Python Programming の74Pに "Checking obj.__class__ is evil" とあり、推奨されていない模様です。
単純に、継承先のクラスで同じ属性/メソッドを定義すればそれでオーバーライドできます。
class S2(object): name = "Spam" def saySpam(self): print 'Spam' class C2(S2): name = "Bacon" def saySpam(self): print 'Bacon'
c = C2() print c.name # >>> Bacon c.saySpam() # >>> Bacon
人からちょっとしたホームページ編集を頼まれて、「こんなんでどうですか~」と見せてて、画像を数点表示しているところで。
「これさ、サムネイル画像をクリックしたら、真ん中に大きな画像がポップアップするみたいに、最近よく見かける感じに出来ない?」
と言われた。
Google先生に「JavaScript image popup」とか「jQuery image popup」とか放り込んだらちょうど良いのが見つかった。
キャプションも付けられるし、ポップアップした時の枠線画像とか閉じる為の「×」画像とかとCSSも用意されているので、サンプルをコピペしてちょこっと直すだけで動く。JavaScriptは、jqueryの最新版のminバージョンも込みで入ってるので、そのまままとめて放り込むだけでOK.
<a href="(大きいサイズの画像URL)"><img src="(サムネイル画像のURL)" /></a>
という要素に対して、
$(document).ready(function() { $("a").fancybox(); });
という感じで適用している。<a>タグに適当なclass属性を割り当てれば、jQueryのselectorで任意のa要素だけに適用できる。
これならJavaScript-OFFの場合でも、サムネイル画像クリックで大きな画像にページ毎遷移するという最低限度の動きは残せる。