2016-02-11 15 views
9

Pythonでは、標準機能とコンテキストマネージャの両方として機能する多くの機能があります。Python:標準関数とコンテキストマネージャ?

my_file=open(filename,'w') 

または

with open(filename,'w') as my_file: 

どちらもあなたがする必要があるものは何でもするために使用することができmy_fileオブジェクトを与える:たとえばopen()は、のいずれかとして呼び出すことができます。一般的には後者が好ましいが、前者もやりたいときがある。

私は__enter____exit__機能を持つクラスを作成することによって、または関数とyieldはなくreturn@contextlib.contextmanagerデコレータを使用するか、コンテキストマネージャを作成する方法を見つけ出すことができました。しかし、私がこれを行うと、私はもはや関数をまっすぐに使うことができません - たとえば、デコレータを使用して、私は_GeneratorContextManagerオブジェクトを返します。もちろん、クラスとして作成した場合、ジェネレータクラスのインスタンスを取得するだけで、基本的には同じものと見なすことができます。

したがって、関数、オブジェクト、またはコンテキストマネージャを返す関数、つまり_GeneratorContextManagerなどを返す関数(またはクラス)を設計するにはどうすればよいですか?

編集:例えば

、私は(これは非常に単純化され)、次のような機能を持っていると言う:

def my_func(arg_1,arg_2): 
    result=arg_1+arg_2 
    return my_class(result) 

ので、関数はそれらをものにし、引数の数をとり、そのスタッフの結果を使ってクラスを初期化し、それを返します。私がopenを呼び出した場合、fileオブジェクトを持っているように、最終結果はmy_classのインスタンスです。私は、コンテキストマネージャとしてこの機能を使用することができるようにしたい場合は、私はそうのようにそれを修正することができます。

コンテキストマネージャとして呼び出したときだけで正常に動作し
@contextlib.contextmanager 
def my_func(arg_1,arg_2): 
    result=arg_1+arg_2 # This is roughly equivalent to the __enter__ function 
    yield my_class(result) 
    <do some other stuff here> # This is roughly equivalent to the __exit__function 

が、私はもはやmy_classときのインスタンスを取得していませんストレート関数として呼び出します。おそらく私は何か間違っているだけでしょうか?

編集2:私はそれに機能を追加する機能など、my_classを完全に制御を持っているん

注意。以下の受け入れられた答えから、私の難しさは基本的な誤解から生まれたと推測することができました。私は何を呼んでも(上記の例ではmy_func)、__exit____enter__の機能が必要でした。これは正しくありません。実際、コンテキストマネージャとして機能するためには、関数が関数を必要とする(上記の例ではmy_class)を返すだけです。

+2

'open'はクラスのインスタンスを返す関数です。 'myfile = open(filename)'または 'open(filename)as myfile'のどちらを使っても、それは同じクラスのインスタンスになります。何も変更されません。 – zondo

+0

@zondo真実、私が書こうとしている機能です。しかし、 '@ contextlib.contextmanager'デコレータで関数をラップし、それを標準関数として呼び出すと、戻ってきたクラスは関数からクラス" I "を返さないことになります。私がコンテクストマネージャーとして呼んだときだけ、そのクラスを取得します。簡単な例を追加します。 – ibrewster

+1

あなたのクラスに '__call__'メソッドを定義してください。 –

答えて

1

あなたに実行しようとしている困難は、コンテキストマネージャ(with foo() as x)と通常の関数(x = foo())の両方として使用する機能のために、関数から返されたオブジェクトは、__enter____exit__の両方を持っている必要があることですメソッド...既存のオブジェクトにメソッドを追加するための一般的な方法はありません。

class ContextWrapper(object): 
    def __init__(self, obj): 
     self.__obj = obj 

    def __enter__(self): 
     return self 

    def __exit__(self, *exc): 
     ... handle __exit__ ... 

    def __getattr__(self, attr): 
     return getattr(self.__obj, attr) 

しかし、それは正確と同じではありませんので、これは微妙な問題が発生します。

一つのアプローチは、メソッドを渡すために__getattr__を使用して、元のオブジェクトに属性ラッパークラスを作成することであるかもしれません元の関数(例えば、isinstanceテストが失敗し、iter(obj)のような組み込み関数が期待通りに機能しないなど)によって返されたオブジェクト。

ここに示されたようにあなたはまた、動的に返されたオブジェクトをサブクラス化することができます:https://stackoverflow.com/a/1445289/71522を:

class ObjectWrapper(BaseClass): 
    def __init__(self, obj): 
     self.__class__ = type(
      obj.__class__.__name__, 
      (self.__class__, obj.__class__), 
      {}, 
     ) 
     self.__dict__ = obj.__dict__ 

    def __enter__(self): 
     return self 

    def __exit__(self, *exc): 
     ... handle __exit__ ... 

しかし、このアプローチは、(リンクの記事で述べたように)あまりにも問題があり、それは私が個人的に「wouldn魔法のレベルです強いの正当性なしで導入することは快適ではありません。

私は、一般的にどちらかの明示的な__enter____exit__メソッドを追加、またはcontextlib.closingのようなヘルパーを使用して好む:

with closing(my_func()) as my_obj: 
    … do stuff … 
+0

ああ、欠けていたキーは、返されたオブジェクトが '__enter__'と' __exit__'関数を必要としていたということでした。だから、 'self'を返す' __enter__'関数を使って、関数が返すクラスにそれらの関数を追加することで、それは動作します! – ibrewster

1

だけ明確にするため:あなたはmy_classを変更することができれば、あなたはもちろんの__enter__/__exit__ディスクリプタを追加しますそのクラス。

あなたは(私はあなたの質問から推測する)my_classを変更することができない場合は、これは私が言及していたソリューションです。

デコレータとして
class my_class(object): 

    def __init__(self, result): 
     print("this works", result) 

class manage_me(object): 

    def __init__(self, callback): 
     self.callback = callback 

    def __enter__(self): 
     return self 

    def __exit__(self, ex_typ, ex_val, traceback): 
     return True 

    def __call__(self, *args, **kwargs): 
     return self.callback(*args, **kwargs) 


def my_func(arg_1,arg_2): 
    result=arg_1+arg_2 
    return my_class(result) 


my_func_object = manage_me(my_func) 

my_func_object(1, 1) 
with my_func_object as mf: 
    mf(1, 2) 

@manage_me 
def my_decorated_func(arg_1, arg_2): 
    result = arg_1 + arg_2 
    return my_class(result) 

my_decorated_func(1, 3) 
with my_decorated_func as mf: 
    mf(1, 4) 
+1

私はかなりmy_classを変更することができます。私の混乱は、私がmy_classを直接呼び出すのではなく、my_classを直接呼び出すのではなく、my_classのインスタンスを返す関数を呼び出して、その関数を関数またはコンテキストとして使用できるようにしたかったからですマネージャー - あなたがopen()関数でできるのと同じように。コンテキストマネージャーとして動作する必要があった*返された*オブジェクトであることを私は気づいていませんでした。私は*呼んでいた*関数がコンテキストマネージャーである必要があると思いました。 – ibrewster

関連する問題