インターフェース
インターフェースとは、それを "供給 (provide)" するオブジェクトが外見上どう振舞えばいいのかを定義するオブジェクトです。インターフェースは次の内容を通じて定義されます。
- ドキュメンテーション文字列(docstring)中の略式ドキュメント
- 属性仕様 (attribute specifications)
- 不変条件(invariant)、インターフェースを供給するオブジェクトが維持しなければならない条件
属性仕様はある特定の属性を定義するものであり、属性名、供給ドキュメント、および属性値に関する制約を記述します。属性仕様の定義方法にはいく通りかありますが、詳しくは後述します。
インターフェースを定義する
インターフェースは 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
- これは最も単純な属性仕様の例です。名前とドキュメンテーション文字列があるだけで、ほかには何も定義されていません。
- bar
- こちらはメソッドです。メソッドは関数のように定義されます。メソッドというのは要するに呼び出し可能であることを義務付けられた属性であり、その形式 (signature) は関数定義によって決まります。
bar には引数 self がないことに注意してください。インターフェースとはオブジェクトが どのように使われれるか を記述したものです。インスタンスメソッドを呼び出す場合、self を渡すことはありません。ですからインターフェースの定義文に self は必要ないのです。インスタンスメソッドの引数 self は Python インスタンスの内部でだけ必要とされるものです。インターフェースはインスタンスだけでなく、そのほかのオブジェクトが供給することもあります。それらのメソッドが必ずしもインスタンスメソッドであるとは限りません。たとえばインターフェースはモジュールに対しても定義できますが、モジュールのメソッドは単なる関数に過ぎません。同様にインスタンスのメソッドがインスタンスメソッドではない場合もあります。
インターフェースで定義された属性にはマッピング構文を使ってアクセスできます。
>>> 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)'
- TODO
メソッドにはもっと良い API が必要であろう。今後の改善事項。
インターフェースの宣言 (declare)
インターフェースを定義した後にはじめて、オブジェクトがそれを供給するという 宣言 (declare) が可能になります。詳しい説明に入る前に用語を確認しておきましょう。
- 供給 (provide)
オブジェクトはインターフェースを 供給 します。あるオブジェクトがあるインターフェースを供給しているということは、一方でインターフェースがそのオブジェクトの振舞いを決めていることになります。つまりインターフェースはそのインターフェースを供給するオブジェクトの振舞いを指示するものなのです。
- 実装 (implement)
よくクラスがインターフェースを 実装 するという言い方をします。あるクラスがあるインターフェースを実装しているということはつまり、そのクラスのインスタンスがインターフェースを供給することになります。オブジェクトはそのクラスが実装したインターフェースを供給します 2 (オブジェクトはクラスによる実装に頼らず、直接インターフェースを供給することも可能です)。
クラスは通常、自分が実装しているインターフェースを供給することはない、ということに注意してください。
またファクトリーについても同様です。呼び出し可能なオブジェクトで、あるインターフェースを供給するオブジェクトを生成するもの、それがインターフェースを実装したファクトリーです。
以上で用語の定義はお終いです。続いてインターフェースを宣言する 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)
[1] | Interface をサブクラス化して使うのは、Python のクラス文を使ってインターフェースを生成するためであり、クラスを作成するためではありません。 特別なインターフェースクラスの呼び出しでインターフェースを 生成されるすることも可能ですが、そうすると(ごくまれに) Interface とは無縁のインターフェースが生成されてしまうケー スがあり得ます。このテクニックに関する議論はこの文書の範疇 を越えています。 |
[2] | クラスはファクトリーです。クラスを呼び出すことによってイ ンスタンスが生成されます。つまり、実装という概念を他のファ クトリーにまで拡張することで、インターフェースは生成され るオブジェクトによって供給される、という宣言を可能にして いるのです。 |
[3] | 目的は代替可能性を維持することです。拡張したインターフェースを供給するオブジェクトは、拡張対象となったインターフェースを供給しているオブジェクトと代替可能でなければなりません。この例の場合、IBaz を供給するオブジェクトは、IBlat を供給するオブジェクトすべてと置き換え可能でなければなりません。 インターフェース実装はこのことを強制はしませんが、互換性のチェックは必ずおこなうべきです。 |