13

常識であるため、Python__del__メソッドは、このメソッドが呼び出されることが保証されていないため、重要なものをクリーンアップするために使用しないでください。別の方法は、いくつかのスレッドで説明されているように、コンテキストマネージャーを使用することです。

しかし、コンテキストマネージャーを使用するようにクラスを書き直す方法がよくわかりません。詳述すると、ラッパークラスがデバイスを開閉し、クラスのインスタンスがそのスコープから外れた場合(例外など)にデバイスを閉じるという単純な(機能しない)例があります。

最初のファイルmydevice.pyは、デバイスを開いたり閉じたりするための標準のラッパークラスです。

class MyWrapper(object):
    def __init__(self, device):
        self.device = device

    def open(self):
        self.device.open()

    def close(self):
        self.device.close()

    def __del__(self):
        self.close()

このクラスは別のクラスによって使用されmyclass.pyます:

import mydevice


class MyClass(object):

    def __init__(self, device):

        # calls open in mydevice
        self.mydevice = mydevice.MyWrapper(device)
        self.mydevice.open()

    def processing(self, value):
        if not value:
            self.mydevice.close()
        else:
            something_else()

私の質問:mydevice.pywithメソッド__enter____exit__メソッドでコンテキストマネージャーを実装する場合、このクラスをどのように処理できmyclass.pyますか?私は次のようなことをする必要があります

def __init__(self, device):
    with mydevice.MyWrapper(device):
        ???

しかし、それをどのように処理するのですか?多分私は何か重要なことを見落としていましたか?または、クラススコープ内の変数としてではなく、関数内でのみコンテキストマネージャーを使用できますか?

4

3 に答える 3

17

__enter__とを実装するクラスを作成する代わりに、contextlib.contextmanagerクラスを使用することをお勧めし__exit__ます。仕組みは次のとおりです。

class MyWrapper(object):
    def __init__(self, device):
        self.device = device

    def open(self):
        self.device.open()

    def close(self):
        self.device.close()

    # I assume your device has a blink command
    def blink(self):
        # do something useful with self.device
        self.device.send_command(CMD_BLINK, 100)

    # there is no __del__ method, as long as you conscientiously use the wrapper

import contextlib

@contextlib.contextmanager
def open_device(device):
    wrapper_object = MyWrapper(device)
    wrapper_object.open()
    try:
        yield wrapper_object
    finally:
        wrapper_object.close()
    return

with open_device(device) as wrapper_object:
     # do something useful with wrapper_object
     wrapper_object.blink()

アットマークで始まる行はデコレータと呼ばれます。次の行の関数宣言を変更します。

withステートメントが検出されると、open_device()関数はステートメントまで実行されますyieldyieldステートメントの値は、オプションのas句のターゲットである変数(この場合は。)で返されますwrapper_object。その後、通常のPythonオブジェクトのようにその値を使用できます。制御が任意のパス(例外のスローを含む)によってブロックを終了すると、関数の残りの本体open_deviceが実行されます。

(a)ラッパークラスが低レベルのAPIに機能を追加しているかどうか、または(b)それが含まれているだけでコンテキストマネージャーを使用できるかどうかはわかりません。(b)の場合、contextlibが自動的に処理するため、おそらく完全に省略できます。その場合、コードは次のようになります。

import contextlib

@contextlib.contextmanager
def open_device(device):
    device.open()
    try:
        yield device
    finally:
        device.close()
    return

with open_device(device) as device:
     # do something useful with device
     device.send_command(CMD_BLINK, 100)

コンテキストマネージャーの使用の99%は、contextlib.contextmanagerを使用して実行できます。これは非常に便利なAPIクラスです(そのようなことを気にするなら、その実装方法は低レベルのPython配管の創造的な使用でもあります)。

于 2013-03-15T08:37:48.913 に答える
4

問題は、クラスで使用しているということではなく、デバイスを「オープンエンド」の方法で残したいということです。デバイスを開いてから、開いたままにしておきます。コンテキスト マネージャーは、何らかのリソースを開き、それを比較的短い方法で使用する方法を提供し、最後に確実に閉じるようにします。既存のコードはすでに安全ではありません。何らかのクラッシュが発生した場合、あなた__del__が呼び出されることを保証できないため、デバイスが開いたままになる可能性があるためです。

デバイスが何であり、どのように機能するかを正確に知らなければ、これ以上言うのは難しいですが、基本的な考え方は、可能であれば、使用する必要があるときにのみデバイスを開き、その後すぐに閉じる方がよいということです. したがって、processing次のようなものに変更する必要があるかもしれません。

def processing(self, value):
     with self.device:
        if value:
            something_else()

self.deviceが適切に作成されたコンテキスト マネージャである場合、 でデバイスを開き、で__enter__閉じる必要があり__exit__ます。withこれにより、ブロックの最後でデバイスが確実に閉じられます。

もちろん、一部の種類のリソースでは、これを行うことはできません (たとえば、デバイスの開閉によって重要な状態が失われたり、操作が遅くなったりするため)。それがあなたのケースである場合、あなたは__del__その落とし穴を使用して生活することにこだわっています. 基本的な問題は、デバイスを「オープンエンド」のままにしておく確実な方法がないことですが、異常なプログラム障害が発生した場合でもデバイスが閉じられることを保証します。

于 2013-03-15T08:21:15.903 に答える
0

あなたが何を求めているのかよくわかりません。withコンテキスト マネージャーのインスタンスはクラス メンバーになることができます。それを好きなだけ多くの句で再利用でき、メソッド__enter__()__exit__()メソッドが毎回呼び出されます。

したがって、これらのメソッドを に追加すると、上記と同じようMyWrapperに で構築できます。MyClassそして、次のようにします。

def my_method(self):
    with self.mydevice:
        # Do stuff here

これにより、コンストラクターで作成したインスタンスの__enter__()andメソッドが呼び出されます。__exit__()

ただし、with句は関数にまたがることしかできません。コンストラクタで句を使用すると、コンストラクタを終了する前withに呼び出されます。__exit__()あなたがそれをしたいなら、唯一の方法は__del__()あなたがすでに述べたようにそれ自身の問題を抱えている を使うことです。必要なときにデバイスを開閉できますがwith、これが要件を満たしているかどうかはわかりません。

于 2013-03-15T08:21:52.143 に答える