インターフェースインターフェースとは、それを "供給 (provide)" するオブジェクトが外見上 どう振舞えばいいのかを定義するオブジェクトです。インターフェースは 次の内容を通じて定義されます。
属性仕様はある特定の属性を定義するものであり、属性名、供給ドキュメント、 および属性値に関する制約を記述します。属性仕様の定義方法にはいく通りか ありますが、詳しくは後述します。 インターフェースを定義するインターフェースは Python の class 文を使って定義します。
>>> import zope.interface
>>> class IFoo(zope.interface.Interface):
... """Foo blah blah"""
...
... x = zope.interface.Attribute("""X blah blah""")
...
... def bar(q, r=None):
... """bar blah blah"""
上記はインターフェース IFoo の定義例です。すべてのインターフェースの 祖先インターフェース zope.interface.Interface をサブクラス化していま す。 object が Python の新スタイルクラスすべての祖先クラスであるのと 同様、`zope.interface.Interface` はすべてのインターフェースの祖先になり ます 1 。インターフェースはクラスではありません。 InterfaceClass のインスタンスです。 >>> type(IFoo) <class 'zope.interface.interface.InterfaceClass'> インターフェースのドキュメンテーション文字列は次のようにして問い合わせ ることができます。 >>> IFoo.__doc__ 'Foo blah blah' インターフェース名の問い合わせは次のようにします。 >>> IFoo.__name__ 'IFoo' モジュール名についても同様です。 >>> IFoo.__module__ '__main__' このインターフェースには次の2つの属性が定義されています。
インターフェースで定義された属性にはマッピング構文を使ってアクセスできます。
>>> x = IFoo['x']
>>> type(x)
<class 'zope.interface.interface.Attribute'>
>>> x.__name__
'x'
>>> x.__doc__
'X blah blah'
>>> IFoo.get('x').__name__
'x'
>>> IFoo.get('y')
インターフェースにある名前が定義されているかどうかは in を使って調べ られます。 >>> 'x' in IFoo True インターフェースの属性として定義された名前はイテレーションを使って取得す ることも可能です。 >>> names = list(IFoo) >>> names.sort() >>> names ['bar', 'x'] インターフェースはクラスではないということを思い出してください。属性定 義にはインターフェースの属性としてアクセスすることができません。 >>> IFoo.x Traceback (most recent call last): File "<stdin>", line 1, in ? AttributeError: 'InterfaceClass' object has no attribute 'x' 定義されたメソッドシグニチャを参照することができます。 >>> bar = IFoo['bar'] >>> bar.getSignatureString() '(q, r=None)'
インターフェースの宣言 (declare)インターフェースを定義した後にはじめて、オブジェクトがそれを供給すると いう 宣言 (declare) が可能になります。詳しい説明に入る前に用語を確認 しておきましょう。
以上で用語の定義はお終いです。続いてインターフェースを宣言する API の解 説に入ります。 インターフェースの実装を宣言するインターフェースの実装宣言は、クラス文の中で implements 関数を使うのが 最も一般的な方法です。 >>> class Foo: ... zope.interface.implements(IFoo) ... ... def __init__(self, x=None): ... self.x = x ... ... def bar(self, q, r=None): ... return q, r, self.x ... ... def __repr__(self): ... return "Foo(%s)" % self.x この例では Foo が IFoo を実装すると宣言しています。つまり Foo の インスタンスは IFoo を供給することになります。宣言によってで宣言のイ ントロスペクションが可能になります。その方法にはいくつかありますが、ま ずインターフェースが、あるクラスによって実装されているかどうかを問い合 わせてみましょう。 >>> IFoo.implementedBy(Foo) True インターフェースが、あるオブジェクトによって供給されているかどうかを調 べることもできます。 >>> foo = Foo() >>> IFoo.providedBy(foo) True もちろん Foo は IFoo を実装しているのであり、供給はしていません。 >>> IFoo.providedBy(Foo) False あるオブジェクトがどのインターフェースを実装しているのかも調べられます。 >>> list(zope.interface.implementedBy(Foo)) [<InterfaceClass __main__.IFoo>] 呼び出し可能ではないオブジェクトを指定した場合、インターフェースの問い 合わせはエラーになります。
>>> IFoo.implementedBy(foo)
Traceback (most recent call last):
...
TypeError: ('ImplementedBy called for non-factory', Foo(None))
>>> list(zope.interface.implementedBy(foo))
Traceback (most recent call last):
...
TypeError: ('ImplementedBy called for non-factory', Foo(None))
同様に、オブジェクト供給しているインターフェースを調べることも可能です。 >>> list(zope.interface.providedBy(foo)) [<InterfaceClass __main__.IFoo>] >>> list(zope.interface.providedBy(Foo)) [] ファクトリーでインターフェースの実装を宣言をすることも可能です。Python 2.4 以降ならデコレータ implementer を使用します。それより前のバージョ ンでは次のようにします。 >>> def yfoo(y): ... foo = Foo() ... foo.y = y ... return foo >>> yfoo = zope.interface.implementer(IFoo)(yfoo) >>> list(zope.interface.implementedBy(yfoo)) [<InterfaceClass __main__.IFoo>] なお、デコレータ implementer が引数を変更してしまう可能性があることに注 意してください。また呼び出し側では常に新たなオブジェクトが生成されるこ とを前提にしてはいけません。 もうひとつの注意点として現時点の implementer にはクラスと一緒使えない という制限があります。: >>> zope.interface.implementer(IFoo)(Foo) ... # doctest: +NORMALIZE_WHITESPACE Traceback (most recent call last): ... TypeError: Can't use implementer with classes. Use one of the class-declaration functions instead. インターフェースの供給を宣言するオブジェクトが直接インターフェースを供給するように宣言することも可能で す。たとえば Foo クラスの __init__ メソッドの振舞いを記述することは 可能でしょうか?これは 実際 IFoo に記述することができません。なぜな ら Foo インスタンスの __init__ メソッドを呼び出すことはないからです。 この場合 __init__ メソッドではなく Foo の __call__ メソッドとして 記述します。: >>> class IFooFactory(zope.interface.Interface): ... """Create foos""" ... ... def __call__(x=None): ... """Create a foo ... ... The argument provides the initial value for x ... ... """ この場合、インターフェースを供給するのは(インスタンスではなく)クラスに なります。したがって、インターフェース宣言は次のようにクラスに対して行 います。 >>> zope.interface.directlyProvides(Foo, IFooFactory) この結果、クラス Foo はインターフェースを供給するようになります。 >>> list(zope.interface.providedBy(Foo)) [<InterfaceClass __main__.IFooFactory>] >>> IFooFactory.providedBy(Foo) True クラスインターフェースはよく使われるため、クラス文の中で宣言できるよ うに特別な関数 classProvides が用意されています。 >>> class Foo2: ... zope.interface.implements(IFoo) ... zope.interface.classProvides(IFooFactory) ... ... def __init__(self, x=None): ... self.x = x ... ... def bar(self, q, r=None): ... return q, r, self.x ... ... def __repr__(self): ... return "Foo(%s)" % self.x >>> list(zope.interface.providedBy(Foo2)) [<InterfaceClass __main__.IFooFactory>] >>> IFooFactory.providedBy(Foo2) True これによい似た関数に、モジュールの定義の中でインターフェースを宣言する moduleProvides があります。その使い方は moduleProvides の zope.interface.__init__ 呼び出し部分を見てください。パッケージ zope.interface はインターフェース IInterfaceDeclaration を供給して います。 クラスからインターフェースを得ているインスタンス上に、追加でインター フェースを宣言したいこともあります。次のようなインターフェース ISpecial を定義してみます。
>>> class ISpecial(zope.interface.Interface):
... reason = zope.interface.Attribute("Reason why we're special")
... def brag():
... "Brag about being special"
既存のインスタンス foo にこの reason と brag 属性を追加します。 >>> foo.reason = 'I just am' >>> def brag(): ... return "I'm special!" >>> foo.brag = brag >>> foo.reason 'I just am' >>> foo.brag() "I'm special!" そしてインターフェースを宣言します。 >>> zope.interface.directlyProvides(foo, ISpecial) オブジェクトが供給しているインターフェースを確認してみると、その中に新 たなインターフェースが含まれていることがわかります。 >>> ISpecial.providedBy(foo) True >>> list(zope.interface.providedBy(foo)) [<InterfaceClass __main__.ISpecial>, <InterfaceClass __main__.IFoo>] オブジェクトが直接供給しているインターフェースは次のようにして知ること ができます。 >>> list(zope.interface.directlyProvidedBy(foo)) [<InterfaceClass __main__.ISpecial>] >>> newfoo = Foo() >>> list(zope.interface.directlyProvidedBy(newfoo)) [] インターフェース宣言の継承クラスを継承した場合、そのインターフェース宣言も一緒に継承されます。 >>> class SpecialFoo(Foo): ... zope.interface.implements(ISpecial) ... reason = 'I just am' ... def brag(self): ... return "I'm special because %s" % self.reason >>> list(zope.interface.implementedBy(SpecialFoo)) [<InterfaceClass __main__.ISpecial>, <InterfaceClass __main__.IFoo>] >>> list(zope.interface.providedBy(SpecialFoo())) [<InterfaceClass __main__.ISpecial>, <InterfaceClass __main__.IFoo>] インターフェース宣言を継承したくないときは implements を使わずに implementsOnly を使ってください。 >>> class Special(Foo): ... zope.interface.implementsOnly(ISpecial) ... reason = 'I just am' ... def brag(self): ... return "I'm special because %s" % self.reason >>> list(zope.interface.implementedBy(Special)) [<InterfaceClass __main__.ISpecial>] >>> list(zope.interface.providedBy(Special())) [<InterfaceClass __main__.ISpecial>] 外部宣言通常、インターフェースの実装はクラス定義の一部として行います。しかしク ラス定義とは別にしたい場合もあります。例えば、クラスを別の人に書いても らう場合などです。そのような場合は classImplements を使います。 >>> class C: ... pass >>> zope.interface.classImplements(C, IFoo) >>> list(zope.interface.implementedBy(C)) [<InterfaceClass __main__.IFoo>] サブクラスでインターフェースを継承したくない場合は classImplementsOnly を使います。 >>> class C(Foo): ... pass >>> zope.interface.classImplementsOnly(C, ISpecial) >>> list(zope.interface.implementedBy(C)) [<InterfaceClass __main__.ISpecial>] デクラレーションオブジェクトインターフェースの宣言をすると デクラレーション (declaration) オブジェ クトが生成されます。宣言に対して問い合わせを行うと、デクラレーションオ ブジェクトが返ってきます。 >>> type(zope.interface.implementedBy(Special)) <class 'zope.interface.declarations.Implements'> デクラレーションオブジェクトとインターフェースオブジェクトは多くの類似 点があります。実際両者はベースクラスを共有しています。デクラレーション オブジェクトはインターフェースの宣言を記述する部分で使うことができます。 あまりよくない例ですが、次のような書き方も可能です。 >>> class Special2(Foo): ... zope.interface.implementsOnly( ... zope.interface.implementedBy(Foo), ... ISpecial, ... ) ... reason = 'I just am' ... def brag(self): ... return "I'm special because %s" % self.reason この宣言は先に zope.interface.implements(ISpecial) 使っておこなっ たものとほとんど同じですが、問い合わせの結果返ってくるインターフェース の順番だけが異なります。 >>> list(zope.interface.implementedBy(Special2)) [<InterfaceClass __main__.IFoo>, <InterfaceClass __main__.ISpecial>] インターフェースの継承インターフェースを別のインターフェースで拡張 (extend) することもできま す。その方法は、ほかのインターフェースをベースインターフェースとして羅 列するだけです。
>>> class IBlat(zope.interface.Interface):
... """Blat blah blah"""
...
... y = zope.interface.Attribute("y blah blah")
... def eek():
... """eek blah blah"""
>>> IBlat.__bases__
(<InterfaceClass zope.interface.Interface>,)
>>> class IBaz(IFoo, IBlat):
... """Baz blah"""
... def eek(a=1):
... """eek in baz blah"""
...
>>> IBaz.__bases__
(<InterfaceClass __main__.IFoo>, <InterfaceClass __main__.IBlat>)
>>> names = list(IBaz)
>>> names.sort()
>>> names
['bar', 'eek', 'x', 'y']
IBaz が eek をオーバーライドしていることに注意してください。 >>> IBlat['eek'].__doc__ 'eek blah blah' >>> IBaz['eek'].__doc__ 'eek in baz blah' この例では eek の互換性を保ちながらオーバーライドしています。インター フェースを拡張するときは、そのインターフェースとの互換性を保つ必要があ ります。 3 インターフェースがあるインターフェースを拡張しているかどうかを調べるに は次のようにします。 >>> IBaz.extends(IFoo) True >>> IBlat.extends(IFoo) False なお、インターフェースが自分自身を拡張することはありません。 >>> IBaz.extends(IBaz) False しかしインターフェースが自分自身を拡張しているものとして扱いたい場合は isOrExtends を使ってください。 >>> IBaz.isOrExtends(IBaz) True >>> IBaz.isOrExtends(IFoo) True >>> IFoo.isOrExtends(IBaz) False インターフェースをイテレートすると、ベースインターフェースで定義されて いるものを含むすべての名前が返ってきます。そのインターフェースが直接定 義している名前 だけ を得たいときは次のように names メソッドを使用し てください。 >>> list(IBaz.names()) ['eek'] 属性仕様 (attribute specifications) の継承インターフェースがベースインターフェースの属性仕様をオーバーライドする場 合もあります。2つのインターフェースが同じ名前の属性を定義している場合、 最も詳細 (specific) なインターフェースから属性が継承されます。例を見て みましょう。 >>> class IBase(zope.interface.Interface): ... ... def foo(): ... "base foo doc" >>> class IBase1(IBase): ... pass >>> class IBase2(IBase): ... ... def foo(): ... "base2 foo doc" >>> class ISub(IBase1, IBase2): ... pass ISub の foo は IBase2 で定義されているものです。IBase2 は IBase より詳 細だからです。: >>> ISub['foo'].__doc__ 'base2 foo doc' これは深さ優先の探索ではないことに注意してください。 ある属性をインターフェースが直接定義しているかどうかを知りたい場合もあ ります。これは direct メソッドを使って調べることができます。
>>> IBase.direct('foo').__doc__
'base foo doc'
>>> ISub.direct('foo')
仕様 (Specifications)インターフェースと宣言は両方とも仕様の特別な形態です。先の例でインター フェースの継承が、宣言と仕様のどちらにでも適用できることを確認しました。 宣言は実のところ、宣言しているそのインターフェースを拡張しているのです。 >>> class Baz: ... zope.interface.implements(IBaz) >>> baz_implements = zope.interface.implementedBy(Baz) >>> baz_implements.__bases__ (<InterfaceClass __main__.IBaz>,) >>> baz_implements.extends(IFoo) True >>> baz_implements.isOrExtends(IFoo) True >>> baz_implements.isOrExtends(baz_implements) True 仕様(インターフェースオブジェクトおよびデクラレーションオブジェクト)は 仕様とすべての祖先インターフェースのリスト __sro__ を供給します。 >>> baz_implements.__sro__ (<implementedBy __main__.Baz>, <InterfaceClass __main__.IBaz>, <InterfaceClass __main__.IFoo>, <InterfaceClass __main__.IBlat>, <InterfaceClass zope.interface.Interface>) タグ付き値 (Tagged Values)インターフェースと属性の説明文は "タグ付き値" による拡張が可能です。こ れは UML からいただいたもので、インターフェースに追加のデータを保持させ られるようになっています。
>>> IFoo.setTaggedValue('date-modified', '2004-04-01')
>>> IFoo.setTaggedValue('author', 'Jim Fulton')
>>> IFoo.getTaggedValue('date-modified')
'2004-04-01'
>>> IFoo.queryTaggedValue('date-modified')
'2004-04-01'
>>> IFoo.queryTaggedValue('datemodified')
>>> tags = list(IFoo.getTaggedValueTags())
>>> tags.sort()
>>> tags
['author', 'date-modified']
関数の属性は、メソッドの属性仕様が作成される際、タグ付き値に変換されま す。
>>> class IBazFactory(zope.interface.Interface):
... def __call__():
... "create one"
... __call__.return_type = IBaz
>>> IBazFactory['__call__'].getTaggedValue('return_type')
<InterfaceClass __main__.IBaz>
不変条件 (Invariants)インターフェースはオブジェクトが供給しなければならない状態を指示するこ ともできます。これは不変条件を使って表わします。不変条件は呼び出し可能 なオブジェクトであり、インターフェースを供給しているオブジェクトから呼 び出されます。条件に反する場合、不変条件は Invalid 例外を発生させます。 次がその例です。 >>> class RangeError(zope.interface.Invalid): ... """A range has invalid limits""" ... def __repr__(self): ... return "RangeError(%r)" % self.args >>> def range_invariant(ob): ... if ob.max < ob.min: ... raise RangeError(ob) このような不変条件を定義したら、インターフェース定義の中で次のように使 用します。
>>> class IRange(zope.interface.Interface):
... min = zope.interface.Attribute("Lower bound")
... max = zope.interface.Attribute("Upper bound")
...
... zope.interface.invariant(range_invariant)
インターフェースは不変条件をチェックするメソッドを備えています。 >>> class Range(object): ... zope.interface.implements(IRange) ... ... def __init__(self, min, max): ... self.min, self.max = min, max ... ... def __repr__(self): ... return "Range(%s, %s)" % (self.min, self.max) >>> IRange.validateInvariants(Range(1,2)) >>> IRange.validateInvariants(Range(1,1)) >>> IRange.validateInvariants(Range(2,1)) Traceback (most recent call last): ... RangeError: Range(2, 1) 複数の不変条件がある場合、最初のものでエラーが起きてもチェックを続行し たいこともあるでしょう。`validateInvariants` の引数にリストを渡すと Invalid 例外発生の際、複数の例外のリストを引数として持つ例外をひとつ だけ発生さるようになります。 >>> errors = [] >>> IRange.validateInvariants(Range(2,1), errors) Traceback (most recent call last): ... Invalid: [RangeError(Range(2, 1))] このリストは個々の例外を表すものです。 >>> errors [RangeError(Range(2, 1))] >>> del errors[:] アダプテーション (Adaptation)インターフェースは、アダプテーション(適合)を実現するための呼び出しが可 能になっています。 構文は PEP 246 の adapt 関数が元になっています。 オブジェクトがアダプテーション不可の場合 TypeError が発生します。
>>> class I(zope.interface.Interface):
... pass
>>> I(0)
Traceback (most recent call last):
...
TypeError: ('Could not adapt', 0, <InterfaceClass __main__.I>)
二番目の引数として代替値が指定されている場合、例外は発生しません。 >>> I(0, 'bob') 'bob' オブジェクトがインターフェースを実装しているときは、そのオブジェクトが 返されます。 >>> class C(object): ... zope.interface.implements(I) >>> obj = C() >>> I(obj) is obj True オブジェクトが __conform__ を実装しているときは、それが使われます。 >>> class C(object): ... zope.interface.implements(I) ... def __conform__(self, proto): ... return 0 >>> I(C()) 0 またアダプテーション用のフックが存在するときは、それが使われます (__adapt__ の項を参照)。
>>> from zope.interface.interface import adapter_hooks
>>> def adapt_0_to_42(iface, obj):
... if obj == 0:
... return 42
>>> adapter_hooks.append(adapt_0_to_42)
>>> I(0)
42
>>> adapter_hooks.remove(adapt_0_to_42)
>>> I(0)
Traceback (most recent call last):
...
TypeError: ('Could not adapt', 0, <InterfaceClass __main__.I>)
__adapt__>>> class I(zope.interface.Interface): ... pass インターフェースは PEP 246 の __adapt__ メソッドを実装しています。 通常このメソッドを直接呼び出すことはありません。PEP 246 の adapt フレー ムワークかインターフェースの __call__ を通じて呼び出されます。 __adapt__ メソッドはオブジェクトをレシーバーに適合させる役割を担います。 デフォルトでは None を返します。 >>> I.__adapt__(0) オブジェクトがインターフェースを供給している場合、次のようになります。 >>> class C(object): ... zope.interface.implements(I) >>> obj = C() >>> I.__adapt__(obj) is obj True 必要に応じたアダプテーション実現のために、アダプタのフックを追加したり 削除したりできます。例として 0 を 42 に変換するだけの単純なフックをイン ストールしてみましょう。フックのインストールは、 リスト adapter_hooks に追加するだけです。 >>> from zope.interface.interface import adapter_hooks >>> def adapt_0_to_42(iface, obj): ... if obj == 0: ... return 42 >>> adapter_hooks.append(adapt_0_to_42) >>> I.__adapt__(0) 42 フックは、アダプタが見つかったらそれを返し、見つからなければ None を返 さなければなりません。 フックをアンインストールするときはリストから削除します。 >>> adapter_hooks.remove(adapt_0_to_42) >>> I.__adapt__(0)
最終更新 2010-01-07 18:25:20 |
zope.interface の README.txt Revision 106769の翻訳。
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
© 2010, Yasushi Iwata