ファイルを最初の引数として取るいくつかの関数のデコレータを書きたいと思います。デコレータはコンテキストマネージャプロトコルを実装する必要があるため(つまり、ラップされた関数をコンテキストマネージャに変換する)、関数をクラスでラップする必要があると考えました。
私はデコレータパターンの経験があまりなく、これまでコンテキストマネージャを実装したことはありませんが、私が書いたものはPython 2.7で機能し、コメントアウトするとPython3.3でも機能しwraps
ます。
from functools import wraps
def _file_reader(func):
"""A decorator implementing the context manager protocol for functions
that read files."""
# @wraps(func)
class CManager:
def __init__(self, source, *args, **kwargs):
self.source = source
self.args = args
self.kwargs = kwargs
self.close = kwargs.get('close', True)
def __enter__(self):
# _file_obj is a little helper that opens the file for reading
self.fsource = _file_obj(self.source, 'r')
return func(self.fsource, *self.args, **self.kwargs)
def __exit__(self, exc_type, exc_value, traceback):
if self.close:
self.fsource.close()
return False
return CManager
行のコメントを解除するときに発生するエラーは、次のwraps
内部で発生しますupdate_wrapper
。
/usr/lib/python3.3/functools.py in update_wrapper(wrapper, wrapped, assigned, updated)
54 setattr(wrapper, attr, value)
55 for attr in updated:
---> 56 getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
57 # Return the wrapper so this can be used as a decorator via partial()
58 return wrapper
AttributeError: 'mappingproxy' object has no attribute 'update'
このようなクラスで関数をラップするために使用できるとドキュメントに記載されていないことは知っていますがfunctools.wraps
、Python 2でも機能します。このトレースバックが正確に何を示しているのか、何をすべきかを誰かに説明してもらえますか?wraps
Pythonの両方のバージョンでの効果を達成するには?
編集:私は間違っていた。上記のコードは、私が望んでいることを実行しません。ビルトインのように、の有無にかかわらず関数を使用できるようにしたいと思います。 with
open
上記のコードは、装飾された関数をコンテキストマネージャーに変換します。私ができるようになりたい:
reader = func('source.txt', arg)
for item in reader:
pass
と同様
with func('source.txt', arg) as reader:
for item in reader:
pass
したがって、私のバージョンのコードは、おそらく次のようになります。
def _file_reader(func):
"""A decorator implementing the context manager protocol for functions
that read files."""
@wraps(func)
class CManager:
def __init__(self, source, *args, **kwargs):
self.close = kwargs.get('close', True)
self.fsource = _file_obj(source, 'r')
self.reader = func(self.fsource, *args, **kwargs)
def __enter__(self):
return self.reader
def __iter__(self):
return self.reader
def __next__(self):
return next(self.reader)
def __exit__(self, exc_type, exc_value, traceback):
if self.close and not self.fsource.closed:
self.fsource.close()
return False
return CManager
私が見落としたものについては、遠慮なくコメントしてください。
注:JFセバスティアンによるクラスバージョンは、次のように機能するようです。
基本的wraps
にクラスからを削除し、次のように変更return CManager
しました。
@wraps(func)
def helper(*args, **kwargs):
return CManager(*args, **kwargs)
return helper