2.1. Python の先進的機能を構成するもの¶
著者 Zbigniew Jędrzejewski-Szmek
この節では、Python の高度と思われる機能について扱います – つまり全ての言語がもっているとは限らない機能で、複雑で込み入ったプログラムやライブラリを書くときに便利となります、かといってプログラムを特殊にしたり複雑にしたりするようなものではありません。
この章は特にプログラミング言語としての Python について扱っていることを強調しておきます – つまり機能は Python 標準の機能として特別な構文によって提供されていて、賢い外部モジュールとしては提供されていません。
Python プログラミング言語、特にその構文の開発プロセスはとても透明性が高いです。提案された変更は Python Enhancement Proposals — PEPs として様々な角度から評価、議論されます。結果としてこの章で述べる機能は実際の問題を解決し、目的をできる限り簡単な形で果たせることが示されてから追加されています。
章の内容
2.1.1. イテレータ、ジェネレータ式、ジェネレータ¶
2.1.1.1. イテレータ¶
イテレーターは iterator protocol に従うものです、つまり next
メソッドを持ち、これが呼ばれるとシーケンスの次の要素を返し、返す要素がなければ StopIteration
を送出します。
イテレーターオブジェクトを使うことで、一度だけのループができます。イテレータは反復の際に状態(位置)を保持し、シーケンスに対するループ内では単一のイテレータオブジェクトだけが必要とされます。つまり、同じシーケンスに対し並行に反復させることもできます。反復のロジックをシーケンス自身と切り離すことで、反復の仕方は複数持つことができます。
コンテナの __iter__
メソッドを呼びだしてイテレーターオブジェクトを作るのはイテレータを取得する最も簡単な方法です。 iter
関数は同じ動作を少ない打鍵数を節約してくれます。:
>>> nums = [1, 2, 3] # note that ... varies: these are different objects
>>> iter(nums)
<...iterator object at ...>
>>> nums.__iter__()
<...iterator object at ...>
>>> nums.__reversed__()
<...reverseiterator object at ...>
>>> it = iter(nums)
>>> next(it)
1
>>> next(it)
2
>>> next(it)
3
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
ループ内で StopIteration
が使われるとループは終わります。しかし明示的に呼び出された場合、イテレータは利用済みとみなされてアクセスすると例外が送出されます。
for..in ループも同様に __iter__
メソッドを利用します。これにより明示的にシーケンスに対する反復ができます。既にイテレータがある場合に同じように for
ループを使いたくなるでしょう。これを実現するためにイテレータは next
に加えて __iter__
と飛ばれるメソッドを持っています、これはイテレータ (self
) を返します。
Support for iteration is pervasive in Python:
all sequences and unordered containers in the standard library allow
this. The concept is also stretched to other things:
e.g. file
objects support iteration over lines.
>>> f = open('/etc/fstab')
>>> f is f.__iter__()
True
file
自身がイテレータであり __iter__
メソッドは別のオブジェクトを生成しません: 単一スレッドでのシーケンシャルアクセスのみが許可されます。
2.1.1.2. ジェネレータ式¶
イテレータを作成するための2つめの方法は ジェネレータ式 で、これは リスト内包表記 に基礎づけられています。より正確にいえばジェネレータ式は丸括弧で囲われているか式である必要があります。丸括弧が利用された場合はジェネレータイテレーターが作成されます。角括弧が使われた場合はプロセスが簡略化されてリストが得られます。:
>>> (i for i in nums)
<generator object <genexpr> at 0x...>
>>> [i for i in nums]
[1, 2, 3]
>>> list(i for i in nums)
[1, 2, 3]
リスト内包表記の構文は 辞書内包表記と集合内包表記 にも拡張されます。波括弧で囲われたジェネレータ式は set
を作成します。 key:value
の形式の対を含むジェネレータ式は dict
を作成します:
>>> {i for i in range(3)}
set([0, 1, 2])
>>> {i:i**2 for i in range(3)}
{0: 0, 1: 1, 2: 4}
一つ 注意 を述べておきます: 古い Python では index 変数 (i
) はジェネレータ式の外にリークします、そしてバージョン3以降では修正されています。
2.1.1.3. ジェネレータ¶
イテレータを生成する3つめの方法はジェネレータ関数を呼び出す方法です。 generator は simple:yield
キーワードを含む関数です。必ず意識する必要があることとして、このキーワードが存在すると関数の性質が根本的に変わります: この; yield
文は呼びだされなかったり、ここまで辿りつくことがなくとも関数はジェネレータだと認識されます。通常の関数が呼びだされるときには、関数本体先頭の命令から実行されます。ジェネレータが呼びだされると実行は関数本体最初の命令実行前に停止します。ジェネレータ関数の呼び出しはイテレータプロトコルに従うジェネレータオブジェクトを作成します。通常の関数呼び出しでは、並行や再帰呼び出しが許可されます。
next
が呼び出されると、関数は最初の yield
までを実行します。 yield
文に遭遇するたびに next
の結果の値が返されます。 yield
文の実行後、関数の実行は中断されます。:
>>> def f():
... yield 1
... yield 2
>>> f()
<generator object f at 0x...>
>>> gen = f()
>>> next(gen)
1
>>> next(gen)
2
>>> next(gen)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
ジェネレータ関数の呼び出して、その経過をみてみましょう。:
>>> def f():
... print("-- start --")
... yield 3
... print("-- middle --")
... yield 4
... print("-- finished --")
>>> gen = f()
>>> next(gen)
-- start --
3
>>> next(gen)
-- middle --
4
>>> next(gen)
-- finished --
Traceback (most recent call last):
...
StopIteration
通常の関数で f()
ですぐさま最初の print
が実行されることような動きと対照的に、 gen
は関数本体を実行することなく代入されます。 gen.next()
が next
で実行されたときのみ最初の yield
までの文が実行されます。次の next
は -- middle --
を表示し、次の yield
で停止します。3番目の next
は -- finished --
を表示し、関数の最後に到達します。次に辿りつくべき yield
がないので、例外が送出されます。
yield の後に何が起きるのでしょう、いつ呼び出し元に制御が渡されるのでしょう? ジェネレータの状態はジェネレータオブジェクトに保存されます。ジェネレータ関数からみると、別スレッドで実行されているようにもみえますが、それは幻で、実際にはシングルスレッドで実行されて、インタプリタは状態を維持し、次の値のリクエストで維持した状態から再開します。
ジェネレータはどうして便利なのでしょうか? イテレータについての記載の中で書いたように、ジェネレータ関数はイテレータオブジェクトを作成するためのやり方の1つに過ぎません。 yield
文でできる全てのことは next
メソッドでも可能です。にもかかわらず関数を使い、インタープリターがイテレータを作るために魔法のように動くことには利点があります。関数の定義は next
や __iter__
メソッドを持つクラスを定義するより短かくかけます。より重要な点としてはジェネレータを書く人にとって状態がローカル変数として維持されることが理解しやすい、という点があります、一方でインスタンスの属性を使う場合はイテレータオブジェクトの next
の呼び出しの際にデータを引き渡しをする必要があります。
イテレータはどうして便利なのかというより広い質問があります。イテレータはループで使うときに、ループが単純にすることで、力を発揮します。イテレータを使うコードは、状態を初期化し、ループが終了したかを判定し、別のところから値を抽出して見つける、といったことをしてくれます。これらによってループの本体の重要な点を際立たせてくれます。さらにイテレータのコードは別の場所で再利用もできます。
2.1.1.4. 双方向のコミュニケーション¶
それぞれの yield
文は呼び出し元に値を渡します。これが (Python 2.2 で実装された) PEP 255 でジェネレータが導入された理由です。しかし、逆方向のコミュニケーションも同様に便利に利用できます。便利さを示すわかりやすい例としてはグローバル変数や共有されている変更可能なオブジェクトのような、外部状態でしょう。直接的にコミュニケーションすることは (2.5 で実装された ) PEP 342 によって可能になりました。これは以前のぱっとしない yield
文を式に変更することで達成されました。ジェネレータが yield
文の実行後に復帰する際に、呼び出し元でジェネレータオブジェクトのメソッドを呼び出して、ジェネレータ 内部 に yield
文の結果を値として渡すことやジェネレータに例外を仕込むことができます。
新しいメソッドの1つめは send(value)
でこれは next()
に似ていますが value
をジェネレータ内の yield
式の値として渡すことができます。つまり g.next()
は g.send(None)
と等価です。
2つめの新しいメソッドは throw(type, value=None, traceback=None)
でこれは以下と等価です:
raise type, value, traceback
yield
文の場所で上記を呼び出す。
(呼び出した場所ですぐさま例外を送出する) raise と違い throw()
はジェネレータを復帰させ、そこで例外を送出します。throw という単語が採用されたのは、別の場所で例外を発生させることを暗示し、他の言語での例外と関連づけられているからです。
ジェネレータ内で例外が発生すると何が起きるのでしょうか? 例外は特定の文を実行する際に明示的に送出することもできますし throw()
メソッドを使って yield
文の時点に仕込むこともできます。どちらの場合も、例外は標準的な作法で伝搬します: except
または finally
節で補足したり、あるいはジェネレータ関数を中断し、呼び出し元に伝搬します。
正確を期すために、ジェネレータイテレーターも close()
メソッドを持つことも重要なのでここで述べておきます、これによりジェネレータがすぐに終了するよう、以降の値を提供しないように強制することができます。これによりジェネレータの状態を抱えたオブジェクトを __del__
メソッドで消すことができます。
send と throw で何がわたされたかを表示するジェネレータを定義してみましょう。:
>>> import itertools
>>> def g():
... print('--start--')
... for i in itertools.count():
... print('--yielding %i--' % i)
... try:
... ans = yield i
... except GeneratorExit:
... print('--closing--')
... raise
... except Exception as e:
... print('--yield raised %r--' % e)
... else:
... print('--yield returned %s--' % ans)
>>> it = g()
>>> next(it)
--start--
--yielding 0--
0
>>> it.send(11)
--yield returned 11--
--yielding 1--
1
>>> it.throw(IndexError)
--yield raised IndexError()--
--yielding 2--
2
>>> it.close()
--closing--
next
と __next__
どっち?
Python 2.x では次の値を取得するイテレータメソッドは next
と呼ばれます。このメソッドはグローバル関数 next
から暗黙的の内に呼び出されます、つまりこの関数は __next__
と呼ばれるべき関数です。グローバル関数 iter
が __iter__
を呼び出すように。この一貫性のなさは Python 3.x で修正され it.next
は it.__next__
になりました。ほかのジェネレータのメソッド – send
と throw
– では状況は複雑です、なぜならこれらはインタプリタから暗黙に呼び出されることはないからです。それでもしかし continue
に引数をとれるようにする構文拡張が提案されています、これはつまりループイテレータの send
メソッドに値が渡されることになります。もしこの拡張が受け入れられると gen.sned
が gen.__send__
になるかもしれません。最後のメソッド close
は明らかに不正確な名前です、これは暗黙的に呼び出されています。
2.1.1.5. ジェネレータの連鎖¶
注釈
(未実装ですが Python 3.3 で受け入れられている) PEP 380 のプレビューです。
ジェネレータを書いていて、2つ目のジェネレータ サブジェネレータ を使ってたくさんの値を yield したい状況を考えます。値を yield することだけが目的なら、ループを使って難なく書けます
subgen = some_other_generator()
for v in subgen:
yield v
しかし、もしサブジェネレータが send()
や throw()
そして close()
を呼び出して呼び出し元と影響しあう場合には、状況は複雑になります。 yield
文は try..except..finally 構造で囲われ前の節でジェネレータ関数を「デバッグ」したような見た目になるでしょう。 そんなコードは PEP 380#id13 でも与えられています、ここでは Python 3.3 で導入された新しい構文はサブジェネレーターから値を適切に yield できることを述べるだけにとどめておきます:
yield from some_other_generator()
上で示した明示的なループと同様に振舞い some_other_generator
から値がつきるまで、値を繰り返し yield しますが send
や throw
そして close
をサブジェネレータに引き渡すこともできます。
2.1.2. デコレータ¶
関数やクラスはオブジェクトなので、いろいろなところに渡すことができます。そしてそれらは変更可能なオブジェクトなので、変更することができます。関数やクラスを作成した組み立てた後で、ただし、その名前を束縛する前に、変更することをデコレートするといいます。
「デコレータ」という名前には2つの事が隠されています – 1つはデコレート作業をする関数、つまり、実際の仕事をする部分ともう1つはデコレーター構文をひもづける役割、つまりアットマーク記号に続けてデコレーター関数を表記する部分です。
関数に対するデコレーター構文を利用することで、関数をデコレートできます:
@decorator # ②
def function(): # ①
pass
関数は①の標準的な方法で定義できます。
②の関数定義の前の
@
で始まる式がデコレーターです。@
の後の部分は単純な式である必要があります、通常ここは関数やクラスの名前になります。この部分は最初に評価され、下に書かれた関数が定義し準備ができたところで、新しく定義した関数オブジェクトを単一の引数としてデコレータが呼び出されます。デコレーターが返す値が関数の元々の名前に結びつけられます。
デコレータは関数にもクラスにも適用できます。クラスでもセマンティクスは同じです – 元々のクラス定義が引数としてデコレータを呼び出し、返されたものが何であれ、元々の名前として割り当てられます。
(PEP 318 で) デコレーター構文が実装される前にも、関数やクラスオブジェクトを一時変数に代入しデコレーターを明示的に呼び出してその返り値を関数の名前の代入することで同じことができました。この方法はタイプ数が多く、デコレートされる関数の名前が一時変数として名前が重複し最低3回使われることになり、間違いを引き起こしやすくなります。ともかく上の例は以下と等価です:
def function(): # ①
pass
function = decorator(function) # ②
デコレータは重ねて書くことができます – 適用順序は下から上、または中から外です。セマンティクスは元々定義した関数が最初のデコレーターに適用されその返り値が2つめのデコレータに渡され、最後のデコレーターから返された値が、それが何であるにせよ、元の名前に結びつけられます。
デコレータ構文は読みやすさも考慮して選ばれています。デコレータは関数の前に指定することで、関数の本体の一部に含まれないことがわかりやすく、関数全体のみに影響を及ぼすことが明らかです。式の先頭が @
で始まることで誤りも起きにくくなります (PEP でいうところの “in your face” です :))。1つ以上のデコレータが適用される場合、それぞれが別の行に配置されるのも読みやすくしています。
2.1.2.1. 元々のオブジェクトの置換や細工¶
デコレータは同じ関数やクラスを返したり、完全に別のオブジェクトを返すこともできます。最初に挙げた使い方としてはデコレーターは関数やクラスが変更可能で属性を追加できるという点を利用できます、例えばクラスにドキュメンテーション文字列を追加するなど。デコレータはオブジェクトを変更しない場合にも便利です、例えばデコレートされたクラスをグローバルなレジストリに登録するなど。2つ目に挙げた使い方としては事実上何でもできてしまいます: 元々の関数に何か別のものを代入する場合、新しいオブジェクトは完全に別のものになります。この動作はデコレーターの本来の目的ではありませんが: デコレートされるオブジェクトを、予測不可能にしないままに、細工したいことがあります。つまり、関数が「デコレート」されることで別の関数に置き換えられる場合、大抵の場合、新しい関数は準備動作後に元の関数を呼び出します。同様にクラスが「デコレート」されると新しいクラスに置き換わるときも、大抵の場合新しいクラスは元のクラスから派生することになります。デコレータが例えばデコレートされた関数の呼び出し記録をとるといったように「毎回」何かをすることが目的の場合には、2つめのデコレータのみがその目的に利用できます。一方で1つめの使い方で十分なら、そうしておく方がいいでしょう、そちらの方が単純にすませられます。
2.1.2.2. デコレータをクラスや関数として実装する¶
デコレータであるために 必要なこと は単一の引数として呼び出されることだけです。つまりデコレータは普通の関数としても、 __call__
メソッドを持つクラスとしても実装できるということです、原理的には lambda 関数でもかまいません。
関数とクラスを使ったアプローチを比較してみましょう。デコレータ式 (@
の後の部分) は名前と関数呼び出しのどちらでもかまいません。単純な名前呼び出しのほうがいいですが(タイプ数が少なくきれいにみえるなどの理由で)、そうすると、引数を利用してデコレータカスタマイズできなくなります。デコレータは関数としては2つのするために引数を使わないときのみ利用できます。デコレータは以下の2つの例のように書くことができます。
>>> def simple_decorator(function):
... print("doing decoration")
... return function
>>> @simple_decorator
... def function():
... print("inside function")
doing decoration
>>> function()
inside function
>>> def decorator_with_arguments(arg):
... print("defining the decorator")
... def _decorator(function):
... # in this inner function, arg is available too
... print("doing decoration, %r" % arg)
... return function
... return _decorator
>>> @decorator_with_arguments("abc")
... def function():
... print("inside function")
defining the decorator
doing decoration, 'abc'
>>> function()
inside function
この2つのデコレータの自明な例は元の関数を返すデコレータに分類されます。これらが新しい関数を返す場合は関数のネストを追加する必要があります、最悪の場合には関数を3つネストすることになります。:
>>> def replacing_decorator_with_args(arg):
... print("defining the decorator")
... def _decorator(function):
... # in this inner function, arg is available too
... print("doing decoration, %r" % arg)
... def _wrapper(*args, **kwargs):
... print("inside wrapper, %r %r" % (args, kwargs))
... return function(*args, **kwargs)
... return _wrapper
... return _decorator
>>> @replacing_decorator_with_args("abc")
... def function(*args, **kwargs):
... print("inside function, %r %r" % (args, kwargs))
... return 14
defining the decorator
doing decoration, 'abc'
>>> function(11, 12)
inside wrapper, (11, 12) {}
inside function, (11, 12) {}
14
_wrapper
関数は固定引数やキーワード引数を受け入れられるように定義されています。一般的にはデコレートされた関数がどんな引数を受け付けるのかを知ることはできません、そのため wrapper 関数は単にラップした関数に引数を渡すだけです。その結果あらわれた引数の一覧はとてもややこしくなります。
関数として定義されたデコレータと比べてクラスで定義されたデコレータをは複雑なデコレータをシンプルに書くことができます。オブジェクトが作成されるときには __init__
メソッドは None
のみを返すことができ、作成されたオブジェクトの型は変更できません。つまりデコレータをクラスとして定義する場合、それを引数なしの形式で利用するのは筋がよくありません: 最終的にデコレートされるオブジェクトは、コンストラクタから呼び出されて返ってくる、デコレートするクラスのインスタンスになり、便利とはいえません。つまり、クラスを使ったデコレータについては、引数がデコレータ式に与えられ __init__
メソッドをデコレータを組み立てるときに使う場合のみを考えれば十分でしょう。
>>> class decorator_class(object):
... def __init__(self, arg):
... # this method is called in the decorator expression
... print("in decorator init, %s" % arg)
... self.arg = arg
... def __call__(self, function):
... # this method is called to do the job
... print("in decorator call, %s" % self.arg)
... return function
>>> deco_instance = decorator_class('foo')
in decorator init, foo
>>> @deco_instance
... def function(*args, **kwargs):
... print("in function, %s %s" % (args, kwargs))
in decorator call, foo
>>> function()
in function, () {}
(PEP 8 の) 通常のルールと対照的にデコレータは関数のように振る舞うクラスとして書かれるので、しばしば小文字から始まる名前で書かれます。
実際には、元の関数を返すようなデコレータのために新しいクラスを作ることはあまり筋がよくありません。オブジェクトは状態を持っているはずなので、そういったデコレータは新しいオブジェクトを返すデコレータを作るときにより便利になります。
>>> class replacing_decorator_class(object):
... def __init__(self, arg):
... # this method is called in the decorator expression
... print("in decorator init, %s" % arg)
... self.arg = arg
... def __call__(self, function):
... # this method is called to do the job
... print("in decorator call, %s" % self.arg)
... self.function = function
... return self._wrapper
... def _wrapper(self, *args, **kwargs):
... print("in the wrapper, %s %s" % (args, kwargs))
... return self.function(*args, **kwargs)
>>> deco_instance = replacing_decorator_class('foo')
in decorator init, foo
>>> @deco_instance
... def function(*args, **kwargs):
... print("in function, %s %s" % (args, kwargs))
in decorator call, foo
>>> function(11, 12)
in the wrapper, (11, 12) {}
in function, (11, 12) {}
このようなデコレータは本当になんでもできます、元の関数の変更や引数の隠蔽、元の関数呼び出しをしてもしなくてもよく、呼び出し後に返り値を握りつぶすこともできます。
2.1.2.3. ドキュメンテーション文字列や元の関数の属性をコピーする¶
デコレータによって元々の関数が置き換えて返される場合、不幸にも元々の関数の名前とドキュメンテーション文字列、引数は失なわれてしまいます。これらの元々の関数の属性の一部は __doc__
(ドキュメンテーション文字列) __module__
そして __name__
(関数の完全な名前) そして __annotations__
(Python 3 で利用可能な関数の引数と返り値についての追加情報) を設定することで、「移植」することができます。これらは functools.update_wrapper
を使うことで自動的に実施できます。
functools.update_wrapper(wrapper, wrapped)
“Update a wrapper function to look like the wrapped function.”
>>> import functools
>>> def replacing_decorator_with_args(arg):
... print("defining the decorator")
... def _decorator(function):
... print("doing decoration, %r" % arg)
... def _wrapper(*args, **kwargs):
... print("inside wrapper, %r %r" % (args, kwargs))
... return function(*args, **kwargs)
... return functools.update_wrapper(_wrapper, function)
... return _decorator
>>> @replacing_decorator_with_args("abc")
... def function():
... "extensive documentation"
... print("inside function")
... return 14
defining the decorator
doing decoration, 'abc'
>>> function
<function function at 0x...>
>>> print(function.__doc__)
extensive documentation
一つ重要なこととして、置換する関数にコピーできる属性には不足があり、それは : 引数の一覧です。デフォルト引数は __defaults__
や __kwdefaults__
属性によって変更することができますが、不幸にも引数の一覧自身は属性として設定することができません。つまり help(function)
は不要な引数の一覧を表示する可能性があります、これによって関数のユーザは混乱する可能性があります。汚いやりかたですが効果的な対策は eval
を利用して動的に wrapper を作ることです。これは外部の decorator
モジュールを利用することで自動化できます。このモジュールは decorator
デコレータを提供してくれます、このデコレータはラッパーを引数にとり、ラッパーを関数シグネチャを維持するデコレータに変換してくれます。
まとめると、デコレータでは常に functools.update_wrapper
か同様の関数の属性をコピーする処理を実施すべきです。
2.1.2.4. 標準ライブラリ内での利用例¶
まず、標準ライブラリの中には便利なデコレータがたくさんあることは述べておくべきことでしょう。3つのデコレータは言語の一部として機能しています:
classmethod
はメソッドを「クラスメソッド」にします、つまりクラスをインスタンス化せずに呼び出すことができます。通常のメソッドが呼び出されると、インタプリタは``self`` をインスタンスオブジェクトの最初の固定引数に入れます。クラスメソッドは呼び出されると、クラス自身をcls
としばしば呼ばれる最初の引数として与えます。クラスメソッドはクラスの名前空間内からアクセスすることができるので、モジュールの名前空間を汚染しません。クラスメソッドはコンストラクタの代替手段として利用することもできます:
class Array(object): def __init__(self, data): self.data = data @classmethod def fromfile(cls, file): data = numpy.load(file) return cls(data)
この方法は
__init__
の多数のフラグを持たせるよりもすっきり書けます。staticmethod
はメソッドを「静的」にすることができます、つまり、基本的に普通の関数として働きますが、クラスの名前空間としてアクセスできるようになります。クラス内部からのみ必要となる関数(_
から始まる名前になるでしょう)として利用する場合、またはユーザにクラスと結びついていると考えて欲しいが実装としてはそうする必要がない場合に利用することができます。property
は getter や setter の問題に対する pythonic な解答です。property
でデコレートされたメソッドは属性アクセスの際に自動的に getter となります。>>> class A(object): ... @property ... def a(self): ... "an important attribute" ... return "a value" >>> A.a <property object at 0x...> >>> A().a 'a value'
この例では
A.a
は読み取り専用の属性です。help(A)
は属性a
のドキュメンテーション文字列を含み、それは getter メソッドから取り込まれます。a
をプロパティとして定義することで呼び出し時に計算すること可能にし、setter が定義されていないことで、プロパティを読み取り専用にする副作用があります。setter と getter を持つには、もちろん2つのメソッドが必要です。Python 2.6 以降なら次の構文が望ましいでしょう:
class Rectangle(object): def __init__(self, edge): self.edge = edge @property def area(self): """Computed area. Setting this updates the edge length to the proper value. """ return self.edge**2 @area.setter def area(self, area): self.edge = area ** 0.5
このやり方は
property
デコレータが getter メソッドを property オブジェクトに置き換えることにより機能します。property は3つのメソッドgetter
setter
そしてdeleter
を持ち、それらはデコレータとして利用することができます。これらは property オブジェクトの属性として getter setter deleter を設定します(fget
,fset
そしてfdel
属性として保存されます)。オブジェクトを設定する際に getter は上記の例のように設定することができます。setter を定義するときにはarea
という property オブジェクトが既にあるのでsetter
メソッドを利用して setter を追加します。これがクラスを作る際に起きていることの全てです。その後、クラスのインスタンスが作成されると、property オブジェクトは特殊化されます。インタプリタが属性へのアクセスや代入、削除を実行するとそれらの実行は property オブジェクトのメソッドに以上されます。
全て明晰に理解するために「デバッグ」例を定義してみましょう:
>>> class D(object): ... @property ... def a(self): ... print("getting 1") ... return 1 ... @a.setter ... def a(self, value): ... print("setting %r" % value) ... @a.deleter ... def a(self): ... print("deleting") >>> D.a <property object at 0x...> >>> D.a.fget <function ...> >>> D.a.fset <function ...> >>> D.a.fdel <function ...> >>> d = D() # ... varies, this is not the same `a` function >>> d.a getting 1 1 >>> d.a = 2 setting 2 >>> del d.a deleting >>> d.a getting 1 1
プロパティはデコレータ構文を広げています。デコレータ構文の領分の1つである – 名前が重複しない – は破られています、しかしこれより良いものは提案されていません。getter や setter そして deleter メソッドに同じ名前を使うことは良いスタイルといえます。
いくつかの新しめの例:
functools.lru_cache
は任意の関数を arguments:answer ペアを制限つきキャッシュしを使ってメモ化します (Python 3.2)functools.total_ordering
はクラスデコレータで順序づけのためのメソッド (__lt__
,__gt__
,__le__
, ...) をそれらのうち利用可能な1つから残りを実装してくれます (Python 2.7)。
2.1.2.5. 関数を非推奨にする¶
ある関数を呼び出したときに非推奨の警告を標準エラー出力に最初の1度出力し、次回から表示しないようにしたい場合を考えます。関数に変更を加えたくない場合、デコレータを使うことができます:
class deprecated(object):
"""Print a deprecation warning once on first use of the function.
>>> @deprecated() # doctest: +SKIP
... def f():
... pass
>>> f() # doctest: +SKIP
f is deprecated
"""
def __call__(self, func):
self.func = func
self.count = 0
return self._wrapper
def _wrapper(self, *args, **kwargs):
self.count += 1
if self.count == 1:
print self.func.__name__, 'is deprecated'
return self.func(*args, **kwargs)
関数として実装することもできます:
def deprecated(func):
"""Print a deprecation warning once on first use of the function.
>>> @deprecated # doctest: +SKIP
... def f():
... pass
>>> f() # doctest: +SKIP
f is deprecated
"""
count = [0]
def wrapper(*args, **kwargs):
count[0] += 1
if count[0] == 1:
print func.__name__, 'is deprecated'
return func(*args, **kwargs)
return wrapper
2.1.2.6. while
ループで余計なものを除くデコレータ¶
ループを走らせてリストを返す関数を考えます。いくつのオブジェクトが必要になるかわからない場合、標準的には以下のようにやることになるでしょう:
def find_answers():
answers = []
while True:
ans = look_for_next_answer()
if ans is None:
break
answers.append(ans)
return answers
ループ本体がコンパクトな内はこれでも十分といえるでしょう。ただ、一旦複雑になってしまうと、これは現実のコードにはよくあることですが、かなり読みにくくなります。これを yield
文を使って単純化することもできるでしょうが、ユーザは明示的に list(find_answers())
を呼び出す必要があるでしょう。
リストを作ってくれるデコレータを定義することができます:
def vectorized(generator_func):
def wrapper(*args, **kwargs):
return list(generator_func(*args, **kwargs))
return functools.update_wrapper(wrapper, generator_func)
関数はこうなります:
@vectorized
def find_answers():
while True:
ans = look_for_next_answer()
if ans is None:
break
yield ans
2.1.2.7. プラグイン登録システム¶
クラスを変更しないクラスデコレータですが、グローバルなレジストリにクラスを登録します。これは元々のオブジェクトを返すデコレータの分類されます:
class WordProcessor(object):
PLUGINS = []
def process(self, text):
for plugin in self.PLUGINS:
text = plugin().cleanup(text)
return text
@classmethod
def plugin(cls, plugin):
cls.PLUGINS.append(plugin)
@WordProcessor.plugin
class CleanMdashesExtension(object):
def cleanup(self, text):
return text.replace('—', u'\N{em dash}')
こうしてデコレータを利用して、プラグイン登録を分散できます。デコレータは動詞ではなく名詞で呼ばれます、なぜならクラスは WordProcessor
のプラグインとして利用するために宣言しているからです。 plugin
メソッドは単にプラグインのリストをクラスに追加します。
plugin の名前自身からわかるように: このプラグインは em-dash の HTML エンティティを Unicode em-dash 文字に置換します。ここで unicode literal notation を利用して unicode データベース上でその名前 (“EM DASH”)を入れています。Unicode 文字が直接入れられると、プログラムのソース内の em-dash とそれを区別することはできなくなります。
参考
より多くの例と文書
- PEP 318 (function and method decorator syntax)
- PEP 3129 (class decorator syntax)
- http://wiki.python.org/moin/PythonDecoratorLibrary
- https://docs.python.org/dev/library/functools.html
- http://pypi.python.org/pypi/decorator
- Bruce Eckel
- Decorators I: Introduction to Python Decorators
- Python Decorators II: Decorator Arguments
- Python Decorators III: A Decorator-Based Build System
2.1.3. コンテキストマネージャ¶
コンテキストマネージャは __enter__
と __exit__
メソッドを持つオブジェクトで with 文で利用することができます:
with manager as var:
do_something(var)
は最も単純な例で以下と等価です:
var = manager.__enter__()
try:
do_something(var)
finally:
manager.__exit__()
いいかえれば PEP 343 で定義されるコンテキストマネージャプロトコルは、重要な do_something
ブロックだけを残し、別のクラスに引き離すことぱっとしない try..except..finally 構造を取り除いてくれます。
__enter__
メソッドは最初に呼び出されます。ここではvar
として代入される値を返すことができます。as
の部分はオプションです: なければ単に__enter__
が返す値は無視されます。with
のすぐ下のコードブロックが実行されるときには、try
句と同じようにコードブロックは最後までうまく動くか break したり continue または return または例外を投げることができます。どうするにせよ、ブロックが終了すると__exit__
メソッドが呼び出されます。例外が投げられた場合、例外に関する情報は__exit__
に渡されます、これについては次の小節で紹介します。通常、finally
句のように例外は無視され__exit__
が終了した後に再送出されます。
終わったらすぐファイルが閉じられるようにするには、こう書きます:
>>> class closing(object):
... def __init__(self, obj):
... self.obj = obj
... def __enter__(self):
... return self.obj
... def __exit__(self, *args):
... self.obj.close()
>>> with closing(open('/tmp/file', 'w')) as f:
... f.write('the contents\n')
こうして with
ブロックから抜けたときに f.close()
が呼び出されるようになります。ファイルを閉じるのはよくある操作なのでこの機能のサポートは既に file
クラスに存在しています。 file
クラスは close
を呼び出す __exit__
メソッドを持ち、コンテキストマネージャとして利用できます。
>>> with open('/tmp/file', 'a') as f:
... f.write('more contents\n')
try..finally
はリソースの解放によく利用されます。多くの異なる場合で似たようなものが実装されます:: __enter__
の段階でリソースが確保され __exit__
の段階で解放され、例外が送出された場合には、それが伝搬されます。ファイルのようにオブジェクトが利用された後に実行するよくある操作で、特に便利なものは組み込みでサポートされています。Python の各リリースで様々なサポートが追加されています。
全ての file-like オブジェクト
file
➔ 自動的に閉じるfileinput
,tempfile
(py >= 3.2)bz2.BZ2File
,gzip.GzipFile
,tarfile.TarFile
,zipfile.ZipFile
ロック
multiprocessing.RLock
➔ ロックとアンロックmultiprocessing.Semaphore
memoryview
➔ 自動解放 (py >= 3.2 and 2.7)
decimal.localcontext
➔ 一時的に計算桁を変更する_winreg.PyHKEY
➔ ハイブキーを開き、閉じるwarnings.catch_warnings
➔ 一時的に警告を抑制しますcontextlib.closing
➔ 上の例と同様にclose
を呼び出します並列プログラミングk
concurrent.futures.ThreadPoolExecutor
➔ 並列に呼び出しされた後にスレッドプールを破棄します (py >= 3.2)concurrent.futures.ProcessPoolExecutor
➔ 並列に呼び出された後にプロセスプールを破棄します (py >= 3.2)nogil
➔ 一時的に GIL の問題を解決します (cython のみで利用できます :( )
2.1.3.1. 例外を捉える¶
with
ブロック内で例外が送出されると、例外は __exit__
の引数として渡されます。引数は3つあり sys.exc_info()
: から返されるものと同じ, type, value, traceback です。例外が投げられない場合は 3つの引数には None
が利用されます。コンテキストマネージャは __exit__
で真値を返すことで、例外を「握りつぶす」こともできます。例外は簡単に無視することができます。 __exit__
が return
を利用せず最後まで到達する場合、偽値である None
が返されるので、例外は __exit__
が終了した後に再送出されることになります。
例外を補足する機能は興味深い可能性の一端を示してくれます。古典的な例はユニットテストです – いくつかのコードが適切な例外を投げるか確かめたい場合:
class assert_raises(object):
# based on pytest and unittest.TestCase
def __init__(self, type):
self.type = type
def __enter__(self):
pass
def __exit__(self, type, value, traceback):
if type is None:
raise AssertionError('exception expected')
if issubclass(type, self.type):
return True # swallow the expected exception
raise AssertionError('wrong exception type')
with assert_raises(KeyError):
{}['foo']
2.1.3.2. コンテキストマネージャの定義にジェネレータを利用する¶
ジェネレータ について議論したときにイテレータをクラスで実装せずに、ジェネレータで実装した方が短かく、素敵に、そして状態をインスタンス変数ではなくローカル変数で保存できて、好ましいと述べました。一方で 双方向のコミュニケーション で書いたようにジェネレーターと呼び出し元の間のデータフローは双方向です。データフローは例外を含んでおり、ジェネレータ内部で送出することができます。コンテキストマネージャーを特別なジェネレータ関数として実装してみましょう。実はジェネレータプロトコルはこのユースケースのためにデザインされています。
@contextlib.contextmanager
def some_generator(<arguments>):
<setup>
try:
yield <value>
finally:
<cleanup>
ヘルパー関数 contextlib.contextmanager
はジェネレータをとり、ジェネレータをコンテキストマネージャに変換してくれます。ジェネレータはラッパー関数としていくつかのルールに従う必要があります – 特に重要なものとして yield
は厳密に一回だけでなければいけません。 yield
の前の部分は __enter__
で実行され、コンテキストマネージャーで保護されたコードブロックは yield
で停止された際に実行されます、そして残りの実行は __exit__
で実行されます。例外が送出された場合はインタプリタは __exit__
の引数経由でラッパーに渡し、ラッパー関数は yield
文の時点で送出します。ジェネレータをこのように使うことでコンテキストマネジャーを短かく単純にすることができます。
closing
の例をジェネレータで書き直してみましょう:
@contextlib.contextmanager
def closing(obj):
try:
yield obj
finally:
obj.close()
assert_raises
の例をジェネレータで書き直してみましょう:
@contextlib.contextmanager
def assert_raises(type):
try:
yield
except type:
return
except Exception as value:
raise AssertionError('wrong exception type')
else:
raise AssertionError('exception expected')
ここで、ジェネレータ関数をコンテキストマネージャにするために、デコレータを使います!